天天看點

java鎖工具類的實作_java多線程無鎖和工具類

1 無鎖

(1) cas (compare and swap) 設定值的時候,會比較目前值和當時拿到的值是否相同,如果相同則設值,不同則拿新值重複過程;注意,在設定值的時候,取值+比較+設值 是一條cpu語句,在這個過程中不會有其他線程幹擾,是原子操作。從指令層面保證操作可靠。CAS有3個操作數,記憶體值V,舊的預期值A,要修改的新值B。

(2)無鎖類 無鎖算法都處于無限循環之内,先進行compare,成功後swap,不成功則一隻循環compare。

AtomicInteger get() set() getAndIncrement() incrementAndGet() compareAndSet().... 封裝了一個volatile的int value,以incrementAndGet()為例,會使用目前值,偏移量,和期望值做判斷是否increament;getAndIncreament() 拿到值以後再加一,用目前值和期望值比較 如果相同則沒被其他線程打擾,不同則重試。

AtomicReference 使用:如a= new AtomicReference("a") ,多線程調用compareAndSet("a","bc") 那麼會使用cas給a對象新的引用,隻有一個線程會修改成功(因為成功之後原始引用就變了 compare會失敗),防止并發修改引用。

AtomicStampedReference stamped是時間戳,對atomicReference的加強。如果一個引用,經過了A→B→A的過程,一個線程在指派進行cas的時候,compare得到後面一個A,認為沒有變化于是set,這種情況對注重結果的義務沒影響,但是對于過程敏感(需要記錄過程變化的業務)的情況相當于忽略了一個變化過程。針對後一種情況,在每個引用改變的時候再加個時間戳,可以标注引用的變化情況,compare的時候即比較原始值又比較時間戳,失敗的話重新拿值。

AtomicIntegerArray int型數組,每個元素都是cas線程安全的。

AtomicIntegerFieldUpdater 讓普通變量也可以原子操作,.newUpdater(xxx.class,"字段名稱") 建立更新器,用反射對某個類的某個字段使用原子操作,同時需要手動對該變量加上volatile關鍵字,使用更新器的increamentAndGet可以實作對該變量的原子操作。

*範例 vector 是加鎖同步工具類,整體加鎖。可以設計一個無鎖vector,LockFreeVector. 内部封裝一個AtomicReference 二維數組,當資料擴充的時候,直接在二維位置上加一個容量大一倍的數組,而不是像一般vector一樣聲明一個兩倍大小的空數組再拷貝原來資料。相當于擁有好多個buckets,每個bucket大小都是前一個的兩倍,總共30個bucket,絕對夠用。擴容時,使用compareAndSet 期望原始值是null,新值是新的長度兩倍的數組。add的時候 先算出應該在哪個bucket和bucket上那個位置,然後還是cas,期望原始值是null實際值為新值。

2 并發包 concurrent

(1)ReentrantLock sychronized更新版,lock=new ReentrantLock; lock.lock();lock.unlock();需要程式控制何時釋放鎖,增強靈活性;

可重入,可以多次lock(),lock幾次就得unlock幾次,每次重入lock值+1。

可中斷,lock.lockinterruptibly(),這個函數加鎖可中斷,抓異常進行中斷,可預防死鎖。

可限時,lock.tryLock(),申請鎖的時間加上限制,避免一直申請不到鎖浪費時間。

公平鎖,保證線程先到先得,但公平鎖要處理排隊,性能比一般鎖差。

*synchronized是在JVM層面上實作的,不但可以通過一些監控工具監控synchronized的鎖定,而且在代碼執行時出現異常,JVM會自動釋放鎖定,但是使用Lock則不行,lock是通過jdk代碼實作的,要保證鎖定一定會被釋放,就必須将unLock()放到finally{}中。

* 在資源競争不是很激烈的情況下,Synchronized的性能要優于ReetrantLock,但是在資源競争很激烈的情況下,Synchronized的性能會下降幾十倍,但是ReetrantLock的性能能維持常态;

(2)Condition 和reentrantlock成對使用,類似wait和notify,使用await()和signal(),挂起線程和喚醒,挂起釋放鎖,喚醒重新争奪鎖。也和wait相似,抓中斷異常來進行中斷;如果調用awaitUninterruptibly() 則不會響應中斷。原理是維護一個等待隊列,把await的線程放入等待隊列,讓出cpu時鐘,signal的時候把等待隊列的第一個放入lock的執行隊列。

1).await()方法是可以響應線程中斷指令的;

2).await()和signal()方法在目前線程鎖定了Condition對應的鎖時才能使用;

3).一個鎖對應多個Condition對象,每個Condition的signal()方法隻通知相同Condition的await()方法,condition之間不會互相通知;

4).signal()被調用時,wait隊列中的第一個對象會被喚醒,signalAll()時,wait隊列中元素的全部按先入先出的順序被喚醒;

5).如果既存在await的線程,又存在一直等待lock()的線程,當signal()的線程完成時,lock()的線程優先級比await()的線程優先級高,當所有lock()線程擷取到鎖并釋放後,才會輪到await()線程。

(3)Semaphore 信号量,允許若幹個線程進入臨界區,如果信号量是1,就是普通的線程(具有排他性)。new Semaphore(5);初始化信号量個數,semp.acquire()和 semp.release()代表拿到信号量和釋放信号量,如果初始信号量為1,代表隻有一個線程可以進入臨界區,跟普通加鎖一樣。信号量可以更靈活的資源配置設定。

(4)ReadWriteLock 允許多讀單寫,所有read線程是無等待并發。new ReentrantReadWriteLock(),讀鎖readLock(),寫鎖writeLock。讀寫互斥,讀讀允許。

讀鎖和寫鎖之間滿足如下的限制:

1)當任一線程持有寫鎖或讀鎖時,其他線程不能獲得寫鎖;

2)當任一線程持有寫鎖時,其他線程不能擷取讀鎖;

3)多個線程可以同時持有讀鎖。

* 擷取寫鎖以後不釋放寫鎖可以繼續擷取讀鎖,但是獲得讀鎖以後不釋放獲得寫鎖不可以。寫鎖可以降級為讀鎖,如果獲得寫鎖在獲得讀鎖,再釋放寫鎖,讀鎖仍然持有。

(5)CountDownLatch 倒數計數器,所有線程都結束在繼續某操作時可以使用。

(6)CyclicBarrier 循環栅欄,countDownLatch 是能設定一次,latch是–,完了以後就沒用了,CyclicBarrier可以循環使用,計數是++。

*使用coutdownlatch,是把分散在各線程的路徑最終統一到主線程(主線程await),cyclicBarrer是在各線程中await,每次await就++,到達初始設定的栅欄值後繼續往下走,相當于所有參與者線程都到了(await)以後才繼續往下走,使用時會抓異常防止某線程中斷無法await導緻其他線程都處在await狀态無法往下走。

(7)LockSupport park() unPark() 挂起 繼續線程,類似suspend,但是suspend有缺陷,如果resume發生在suspend之前,那麼suspend挂起後會永遠挂起;LockSupport如果先unPark 在park,不會挂起不動。park/unpark的設計原理核心是“許可”。park是等待一個許可。unpark是為某線程提供一個許可。如果某線程A調用park,那麼除非另外一個線程調用unpark(A)給A一個許可,否則線程A将阻塞在park操作上。