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開銷大,不能保證兩個或者以上變量的原子性操做;