Java JUC(java.util.concurrent)上篇
1. JUC 簡介
在 Java 5.0 提供了 java.util.concurrent(簡稱JUC)包,在此包中增加了在併發程式設計中很常用的工具類,
用於定義類似於執行緒的自定義子系統,包括執行緒池,非同步 IO 和輕量級任務框架;還提供了設計用於多執行緒上下文中的 Collection 實現等;
volatile 關鍵字
volatile 關鍵字: 當多個執行緒進行操作共享資料時,可以保證記憶體中的資料是可見的;
volatile 不具備"互斥性";
volatile 不能保證變數的"原子性";
相較於 synchronized
i++ 的原子性問題
i++的操作實際上分為三個步驟: “讀-改-寫”;
原子性: 就是"i++"的"讀-改-寫"是不可分割的三個步驟;
原子變數: JDK1.5 以後, java.util.concurrent.atomic包下,提供了常用的原子變數;
原子變數中的值,使用 volatile 修飾,保證了記憶體可見性;
CAS(Compare-And-Swap) 演算法保證資料的原子性;
Atomic : AtomicInteger
Locks : Lock, Condition, ReadWriteLock
Collections : Queue, ConcurrentMap
Tools : CountDownLatch, CyclicBarrier, Semaphore
原子操作
多個執行緒執行一個操作時,其中任何一個執行緒要麼完全執行完此操作,要麼沒有執行此操作的任何步驟,那麼這個操作就是原子的。出現原因: synchronized的代價比較高。
以下以AtomicInteger為例:
- int addAndGet(int delta):以原子方式將給定值與當前值相加。 實際上就是等於執行緒安全版本的i =i+delta操作。
- boolean compareAndSet(int expect, int update):如果當前值 ==
- int decrementAndGet():以原子方式將當前值減 1。 相當於執行緒安全版本的–i操作。
- int getAndAdd(int delta):以原子方式將給定值與當前值相加。
相當於執行緒安全版本的t=i;i+=delta;return t;操作。 - int getAndDecrement():以原子方式將當前值減 1。 相當於執行緒安全版本的i–操作。
- int getAndIncrement():以原子方式將當前值加 1。 相當於執行緒安全版本的i++操作。
- int getAndSet(int newValue):以原子方式設定為給定值,並返回舊值。
相當於執行緒安全版本的t=i;i=newValue;return t;操作。 - int incrementAndGet():以原子方式將當前值加 1。 相當於執行緒安全版本的++i操作。
指令重排
你的程式並不能總是保證符合CPU處理的特性。
要程式的最終結果等同於它在嚴格的順序化環境下的結果,那麼指令的執行順序就可能與程式碼的順序不一致。
CAS操作
Compare and Swap
CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。
實現簡單的非阻塞演算法:
privatevolatileintvalue;// 藉助volatile原語,保證執行緒間的資料是可見的
publicfinalintget() {
returnvalue;
}
publicfinalintincrementAndGet() {
for(;;) {
intcurrent = get();
intnext = current +1;
if(compareAndSet(current, next))
returnnext;
}//Spin自旋等待直到返為止置
}
整個JUC都是建立在CAS之上的,對於synchronized阻塞演算法,J.U.C在效能上有了很大的提升。會出現所謂的“ABA”問題
Lock 鎖
Synchronized屬於獨佔鎖,高併發時效能不高,JDK5以後開始用JNI實現更高效的鎖操作。
Lock—->
ReentrantLock—->
ReentrantReadWriteLock.ReadLock / ReentrantReadWriteLock.writeLock
ReadWriteLock—-> ReentrantReadWriteLock
LockSupport
Condition
方法名稱 作用
void lock() 獲取鎖。如果鎖不可用,出於執行緒排程目的,將禁用當前執行緒,並且在獲得鎖之前,該執行緒將一直處於休眠狀態。
void lockInterruptibly() throws InterruptedException; 如果當前執行緒未被中斷,則獲取鎖。如果鎖可用,則獲取鎖,並立即返回。
Condition newCondition(); 返回繫結到此 Lock 例項的新 Condition
例項
boolean tryLock(); 僅在呼叫時鎖為空閒狀態才獲取該鎖
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 如果鎖在給定的等待時間內空閒,並且當前執行緒未被中斷,則獲取鎖
void unlock(); 釋放鎖
PS : 一般來說,獲取鎖和釋放鎖是成對兒的操作,這樣可以避免死鎖和資源的浪費。
注:在 finally 裡面做釋放鎖的操作
AQS
鎖機制實現的核心所在。AbstractQueuedSynchronizer是Lock/Executor實現的前提。
AQS實現:
基本的思想是表現為一個同步器,AQS支援下面兩個操作:
acquire:
while(synchronization state does not allow acquire){
enqueue current threadifnot already queued;
possibly block current thread;
}
dequeue current threadifit was queued;
release:
update synchronization state;
if(state may permit a blocked thread to acquire)
unlock one or more queued threads;
要支援這兩個操作,需要實現的三個條件:
Atomically managing synchronization state(原子性操作同步器的狀態位)
Blocking and unblocking threads(阻塞和喚醒執行緒)
Maintaining queues(維護一個有序的佇列)
Atomically managing synchronization state
使用一個32位整數來描述狀態位:private volatile int state; 對其進行CAS操作,確保值的正確性。
Blocking and unblocking threads
JDK 5.0以後利用JNI在LockSupport類中實現了執行緒的阻塞和喚醒。
LockSupport.park() //在當前執行緒中呼叫,導致執行緒阻塞
LockSupport.park(Object)
LockSupport.unpark(Thread)
Maintaining queues
在AQS中採用CHL列表來解決有序的佇列的問題。(CHL= Craig, Landin, and Hagersten)