java.util.concurrent
是Java爲開發者提供的一些高效的工具類,其實現大多基於前文分析的volatile+CAS來實現鎖操做。java
java.util.concurrent
包含兩個子包,java.util.concurrent.atomic
和 java.util.concurrent.locks
。編程
java.util.concurrent.atomic
是一些原子操做類,好比AtomicBoolean、AtomicInteger、AtomicLong、AtomicIntegerArray等基本數據類型包裝類的原子操做 以及 AtomicReference、AtomicReferenceArray等的普通java對象的原子操做類。JDK1.8新增了一些用於並行計算的DobleAccumulator、DoubleAdder、LongAccumulator、LongAdder等類。數組
java.util.concurrent.locks
是一些Lock接口及其實現類。可用於開發的實現類包括以前介紹過的 ReentrantLock以及ReentrantReadWriteLock等類,以及Lock相關的工具類LockSupport。安全
除了這兩個包以外,java.util.concurrent
包下還包括許多的併發類。好比用於Queue的實現類、用於併發流程控制信號類的CountDownLatch、CyclicBarrier、Semaphore類等、用於併發安全集合類的ConcurrentHashMap、CopyOnWriteArrayList類等、用於線程池的Excutors、ThreadPollExecutor類等、用於執行並行任務類的ForkJoinPool、ForkJoinTask等。多線程
本文首先介紹java.util.concurrent.atomic
包提供的原子類。併發
單線程執行代碼i = i+1
是不會出現意外狀況的。可是在多線程語境下,可能獲得指望以外的值,好比變 量i=1,A線程更新i+1,B線程也更新i+1,通過兩個線程操做以後可能i不等於3,而是等於2。 由於A和B線程在更新變量i的時候拿到的i都是1,這就是線程不安全的更新操做。能夠在方法上增長synchronized來解決,由synchronized來保證多線程不會同時更新變量i。可是,單單爲了這一個操做就將整個方法或者代碼塊增長synchronized同步,效率會比較低。因而,java爲開發者推出了原子類包java.util.concurrent.atomic
,旨在解決這一問題,其底層使用的就是前文提到過的volatile+CAS操做。工具
1,AtomicBoolean、AtomicInteger、AtomicLong,這三個基本類型原子類的方法幾乎如出一轍。AtomicInteger的經常使用方法有:atom
void set(int newValue); // 等同於 賦值 i = newValue; int getAndIncrement(); // 等同於 j = i++; int getAndDecrement(); // 等同於 j = i--; int getAndAdd(int delta); // 等同於 j = i; i = i + delta; int incrementAndGet(); // 等同於 j = ++i; int decrementAndGet(); // 等同於 j = --i; int addAndGet(int delta); // 等同於 i = i + delta; j = i; boolean compareAndSet(int expect, int update); // 直接經過CAS操做來比較 int getAndSet(int newValue); // 等同於 j = i; i = newValue;
2,AtomicReference<V>、AtomicStampedReference<V>,這兩個是引用類型原子類。後者是前者的補充,是帶有版本號的CAS操做,用於解決只帶有值的CAS操做的ABA問題。AtomicReference<V>的經常使用方法有:線程
V get(); void set(V newValue); boolean compareAndSet(V expect, V update); V getAndSet(V newValue);
AtomicStampedReference<V>的經常使用方法有:code
V getReference(); int getStamp(); boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp); void set(V newReference, int newStamp);
3,**AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray<E>**是數組類的原子類。AtomicIntegerArray的經常使用方法有:
int get(int i); // 獲取數組索引 i 處的值 void set(int i, int newValue); int getAndSet(int i, int newValue); boolean compareAndSet(int i, int expect, int update); int getAndIncrement(int i); int getAndDecrement(int i); int getAndAdd(int i, int delta); int incrementAndGet(int i); int decrementAndGet(int i); int addAndGet(int i, int delta);
4,**AtomicIntegerFieldUpdater<T>、AtomicLongFieldUpdater<T>、AtomicReferenceFieldUpdater<T,V>**這三個是字段級別的原子類。能夠對<T>類型的class的指定字段進行原子更新操做。不過這三個類都是抽象類,須要使用靜態方法構指明class和field構建後才能使用。AtomicIntegerFieldUpdater<T>的經常使用方法有:
static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName); // 靜態方法構指明class和須要原子更新的field boolean compareAndSet(T obj, int expect, int update); void set(T obj, int newValue); int get(T obj); int getAndSet(T obj, int newValue); int getAndAdd(T obj, int delta); int getAndIncrement(T obj); int getAndDecrement(T obj); int incrementAndGet(T obj); int decrementAndGet(T obj); int addAndGet(T obj, int delta);
原子操做類是synchronized同步鎖的替代者之一,同時java.util.concurrent.locks
提供了Lock接口,實現更爲豐富的關於同步鎖的操做,提供更爲高級的synchronized的替代選項。
1,Lock接口最經常使用的實現類是 ReentrantLock 和 ReentrantReadWriteLock。前者表示重入鎖,後者表示可重入的讀寫鎖。Lock接口的API以下:
void lock(); // 阻塞獲取鎖,直到得到鎖。 void lockInterruptibly() throws InterruptedException; // 阻塞獲取鎖,直到得到鎖。但在等在獲取鎖的時候能夠響應線程的中斷標識。 boolean tryLock(); // 嘗試獲取鎖。若是成功得到鎖則返回true。若是沒有得到鎖則返回false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 一段時間內嘗試獲取鎖。若是時間段內得到鎖則返回true。超時則返回false。 void unlock(); // 釋放鎖 Condition newCondition(); // 建立一個綁定在該lock對象上的condition對象。
Condition接口是Lock接口的一個組件,是對Lock功能各位豐富的補充。一個線程只有在獲取到lock對象以後,纔可使用綁定在該lock接口上的condition對象的方法。Condition接口的經常使用API以下:
void await() throws InterruptedException; // 當前線程進入等待狀態,直到被喚醒或中斷 void awaitUninterruptibly(); // 當前線程進入等待狀態,直到被喚醒,不響應線程中斷標識 long awaitNanos(long nanosTimeout) throws InterruptedException; // 一段時間內處於等待狀態。超時自動喚醒,容許被喚醒。返回值 = nanosTimeout - 喚醒時的時間 boolean await(long time, TimeUnit unit) throws InterruptedException; // 一段時間內處於等待狀態。超時自動喚醒則返回false,被其餘線程喚醒則返回true boolean awaitUntil(Date deadline) throws InterruptedException; // 當前線程進入等待狀態,直到指定時間點。若是到達指定時間點自動喚醒,則返回false,被其餘線程喚醒則返回true void signal(); // 喚醒等待該condition對象的其中一個線程 void signalAll(); // 喚醒等待該condition對象的全部線程
獲取一個Condition只能經過Lock的newCondition()方法。同一lock對象上能夠綁定多個condition對象,但同一時間最多隻有一個condition對象能夠執行程序。
2,ReentrantLock表示可重入鎖。持有該對象鎖的線程能夠對資源進行重複加鎖。在該類的構造方法中能夠指定公平鎖和非公平鎖。
2.1,可重入性。
線程執行方法A,使用lock.lock()方法成功獲取到鎖,而後調用方法B,當方法B內執行到lock.lock()的時候(這兩個lock是經過一個lock對象),若是能順利執行,則說明該lock對象具備可重入性。不然不具備可重入性。可類比synchronized關鍵字進行理解,由於synchronized關鍵字屬於JVM層面的隱性的具備可重入性。tips:千萬不要忘記在finally中使用lock.unlock()進行鎖資源的釋放。
2.2,公平鎖和非公平鎖。
ReentrantLock內部是經過AbstractQueuedSynchronizer同步器(AQS)來實現的。AQS同步器內部維護了一個FIFO(先入先出)的阻塞隊列 和 一個int類型的狀態碼。所謂的公平性,就是當隊列不爲空的時候,若是有新的線程爭奪鎖資源,則該新的線程必須加入到隊列的尾部。當有鎖釋放的時候,只能由隊列的頭部對應的線程來進行鎖資源的獲取。所謂的非公平性,就是當有鎖釋放的時候,隊列的頭部所對應的線程 和 新的線程 均可以爭奪鎖資源。若是新的線程成功得到鎖對象則執行後續操做,若是獲取鎖對象失敗則加入到隊列尾部。
因爲AQS同步器獲取鎖資源的操做是經過CAS操做來完成的,剛釋放鎖的線程再次獲取鎖的概率會很是大,使得其餘線程只能在同步隊列中等待。因此非公平鎖的效率要優於公平鎖,吞吐量更大。
3,ReentrantReadWriteLock表示可重入的讀寫鎖。具備可重入性,構造方法中能夠指定公平鎖和非公平鎖。ReentrantReadWriteLock內部維護了一對鎖,一個寫鎖和一個讀鎖。容許多個線程對資源進行讀的訪問,只有一個線程對資源進行寫操做。
3.1,可重入性。 同ReentrantLock。
3.2,公平鎖和非公平鎖。同ReentrantLock。
3.3,鎖降級。
當一個線程在持有了寫鎖並對資源進行寫操做以後,再去獲取讀鎖,而後再釋放寫鎖。這個過程稱爲是鎖降級。鎖降級中讀鎖的獲取是否必要呢?答案是必要的,主要是爲了保證數據的可見性。當線程A獲取寫鎖並進行寫操做以後釋放鎖,可是獲取讀鎖的時候失敗,由於線程B獲取到了寫鎖。這種狀況下,線程B對資源進行寫以後,線程A是並不知道的,線程A會使用以前的「髒數據」,會形成併發安全問題。
4,LockSupport是操做線程狀態的併發工具類。經常使用的API以下:
static void unpark(Thread thread); // 喚醒處於阻塞狀態的線程thread static void park(); // 阻塞當前線程,直到被喚醒或中斷 static void parkNanos(long nanos); // 阻塞當前線程,最長不超過nanos納秒 static void parkUntil(long deadline); // 阻塞當前線程,最長不超過deadline時間點 // 如下爲JDK1.6新增API,參數blocker是用來標識當前線程在等待的對象,主要用於問題排查和系統監控。 static void park(Object blocker); // 同park() static void parkNanos(Object blocker, long nanos); // 同parkNanos(long nanos) static void parkUntil(Object blocker, long deadline); // 同parkUntil(long deadline)
LockSupport線程阻塞採用的是Unsafe類的native操做,與JVM層面的object.wait()使線程阻塞不一樣。即,使用LockSupport.park(Object blocker)後阻塞的線程,不能經過blocker.notify()喚醒,反之亦然。
參考資料: