對Java提供的鎖機制的一些思考

Java的數據會在CPU、Register、Cache、Heap和Thread stack之間進行復制操做,而前面四個都是在Java Threads之間共享,所以Java的鎖機制主要用於解決Racing Threads的數據一致性。編程

基於CPU緩存一致性MESI協議的volatile關鍵字緩存

保證變量在racing thread之間實時可見,使用內存屏障禁止JVM基於instruction reorder的優化,不能保證變量在各個線程之間的數據一致性(如counter++);安全

基於字節碼機制的synchronized關鍵字
synchronized是語言自帶的內置獨享鎖、非公平鎖,也就是無論racing thread排隊的時間前後都統一進行鎖競爭,經過編排JVM字節碼實現,鎖爲對象或者類的頭標記位;悲觀鎖,併發性差;另外synchronized聲明不會被繼承,也就是若是子類方法重寫父類的synchronized方法時,再也不具備同步性。多線程

使用synchronized關鍵字修飾代碼塊或者方法,表示這塊代碼爲互斥區或臨界區。有兩種類型的鎖能夠經過synchronized加到代碼塊或者方法上,一種是實例Object鎖,一種是class鎖。對於同一個ClassLoader下加載的類而言,一個類只有一把class鎖,全部這個類的實例都共享一把類鎖;同一個類能夠實現多個實例對象,也就存在多個實例鎖。另外經過synchronized添加的鎖具備可重入性,也就是隻要一個線程已經獲取了鎖,這樣只要共享同一把鎖的其餘synchronized修飾的代碼塊或者方法均可以進入,換句話說其餘線程訪問對其餘synchronized修飾的代碼塊或者方法也須要等待鎖的釋放,所以synchronized還支持任意對象的鎖,這樣同一個類的不一樣方法能夠添加不一樣的對象鎖。併發

下面是基於不一樣鎖實現的synchronized塊:對象鎖,靜態對象鎖,類鎖框架

 1 public class App1 {
 2     private Object lock1 = new Object();
 3     private static Object lock2 = new Object();
 4     synchronized public void funcA() {
 5         //this object lock
 6     }
 7     public void funcB() {
 8         synchronized(this) {
 9             //this object lock
10         }
11         //run something without lock
12     }
13     public void funcC(List<String> list) {
14         synchronized(list) {
15             //list object lock
16         }
17     }
18     public void funcD() {
19         synchronized(lock1) {
20             //lock1 object lock
21         }
22     }
23     public void funcE() {
24         synchronized(lock2) {
25             //lock2 static object lock
26         }
27     }
28     public void funcF() {
29         synchronized(App1.class) {
30             //App1 class lock
31         }
32     }
33     synchronized public static void funcG() {
34         //App1 class lock
35     }
36 }

 

基於Abstract Queued Synchronizer(AQS)機制的ReentraintLock
AQS是JDK提供的的一種實現,能夠實現公平和非公平鎖,內部實現依賴volatile int state變量加CLH隊列實現,主要的實現類是ReentrantLock;AQS定義了多線程訪問共享資源的同步器框架,常見的如ReentraintLock/Semaphore/CountDownLatch等都依賴於AQS的實現;ide

 1 private volatile int state;
 2 static final class Node {
 3     int waitStatus;
 4     Node prev;
 5     Node next;
 6     Node nextWaiter;
 7     Thread thread;
 8 }
 9 protected final int getState() { return state; }
10 protected final void setState(int newState) { state = newState; }
11     
12 protected final boolean compareAndSetState(int expect, int update) {
13         // See below for intrinsics setup to support this
14         return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
15 }
16 protected boolean tryAcquire(int arg) {}
17 protected boolean tryRelease(int arg) {}
18 protected int tryAcquireShared(int arg) {}
19 protected boolean tryReleaseShared(int arg) {}
20 protected boolean isHeldExclusively() {}

 

AQS經過維護一個FIFO隊列,而且經過一個由volatile修飾的int狀態值來實現鎖的獲取,int狀態值經過CPU的compare-and-swap指令進行更新,從而保證原子性。FIFO隊列中每個Node表示一個排隊線程,其保存着線程的引用和狀態,而後經過三個方法分別對獲取或者設置狀態。經過對getState,setState和compareAndSetState的封裝,AQS的繼承類須要試下以下幾個方法,前面兩個表示獲取和釋放獨佔鎖(如ReentraintLock),後面兩個表示獲取和釋放共享鎖(如Semaphore和CountDownLatch)。優化

ReentrantLock初始化狀態state=0,線程A訪問同步代碼的時候使用ReentrantLock.lock(),內部會調用tryAcquire嘗試獲取獨佔鎖,狀態變成state+1;其餘線程調用ReentrantLock.lock()的時候就會失敗,直到線程A調用unlock(內部爲tryRelease)將狀態編程state=0;若是線程A在持有獨佔鎖的同時訪問其餘同步代碼塊,這時候state的值就會累加,須要調用unlock(內部爲tryRelease)減小state的值。ReentrantLock也提供了相似wait/notify的方法,await/signal,一樣的線程在調用這兩個方法以前須要得到對象鎖監視,也就是執行lock.lock()方法。ui

ReentrantLock是純粹的獨佔鎖,爲了提高效率引入了ReentrantReadWriteLock –> readLock/writeLock,讀讀共享,讀寫互斥,寫寫互斥。CLH隊列中的節點模式分爲shared和exclusive兩種,當一個線程修改了state狀態則表示成功獲取了鎖,若是線程的模式是shared則會執行一個傳遞讀鎖的過程,策略是從CLH隊列的頭到尾依次傳遞讀鎖,直到遇到一個模式爲exclusive的寫鎖模式的節點,這個exclusive模式的節點須要等以前全部shared模式的節點對應的操做都執行完畢以後纔會獲取到鎖,這就是讀寫鎖的模式。this

 1 public class App1 extends Thread {
 2     private Lock lock = new ReentrantLock();
 3     private Condition condition = lock.newCondition();
 4     public App1() {
 5         super();
 6     }
 7     @Override
 8     public void run() {
 9         try {
10             lock.lock();
11             System.out.println(Thread.currentThread().getName() 
12                 + " : start to wait.");
13             condition.await();//condition.signal();
14             System.out.println(Thread.currentThread().getName()
15                 + " : wait ends, execute again.");
16         } catch (Exception e) {
17         } finally {
18             lock.unlock();
19         }
20     }
21 }

 

基於Compare-And-Swap機制的AtomicBoolean/AtomicReference/AtomicInteger,可實現自旋鎖(spin-lock),樂觀鎖
基於volatile關鍵字的變量雖然能夠保證各個線程的實時可見,但一旦發生更新操做,則可能發生線程間數據不一致的發生,常見的一種場景是多線程共享的計數器,volatile不能友好解決counter++這樣的更新操做,這個時候可使用AtomicInteger來保證;AtomicInteger內部結合volatile修飾int變量,native類型的unsafe.compareAndSwapInt操做,和基於內存偏移量的compare-and-swap操做實現樂觀鎖的原子操做,最終能夠保證Atomic類數據更新的線程安全;

下面代碼是AtomicInteger類全部封裝操做的內部實現,目標變量的內存值能夠經過aInteger和objectValueOffset惟一肯定,首先取出指望值current,而後經過自旋操做對比當前線程的指望值current與當前內存中的值是否匹配,若是匹配則更新爲current + increment,不然繼續自旋;排隊自旋鎖(ticket spin-lock)正是基於這一機制實現的;

1 public final int getAndAddInt(Object aInteger, long objectValueOffset, 
2         int increment) {
3     int current;
4     do {
5         current = this.getIntVolatile(aInteger, objectValueOffset);
6     } while (!this.compareAndSwapInt(aInteger, objectValueOffset, 
7                     current, current + increment));
8     return current;
9 }

相比synchronized關鍵字的悲觀鎖實現,CAS使用樂觀鎖機制在併發性方面具備比較大的優點,但受限於CAS的實現機制,AtomicInteger也存在一些問題,ABA問題,CPU開銷大,不能保證兩個或者以上變量的原子性操做;

相關文章
相關標籤/搜索