Categories
程式開發

Java-技術專題-AQS和Volatile和Synchronized實現原理


本文參考:

JUC學習(八):AQS的CLH隊列

並發編程——詳解AQS CLH 鎖JMM和底層實現原理

質量管理體系

ReentrantLock類關於lock接口的操作都交給了內部類Sync類來實現,Sync類又有兩個子類NonFairSync,FairSync,公平鎖和不公平鎖;

abstract static class Sync extends AbstractQueuedSynchronizer
static final class NonfairSync extends Sync
static final class FairSync extends Sync

AQS重要成員變量

private transient volatile Node tail; // CLH队列
private volatile int state; // 锁的状态

AQS使用的設計模式:

模板方法設計模式:定義一個代碼模板結構,相同部分在父類實現,不同部分由子類實現

模板方法模式(Template Method) – 最易懂的設計模式解析“。

安卓中的View,Activity都使用了模板方法設計模式,View類規範了所有View需要實現的行為,View的子類可以在onMeasure,onLayout,onDraw中擴展各自不同的行為;體現了設計模式的開閉原則AQS抽像類為子類提供了tryAcquire,tryRelease去擴展自己的不同行為

NonfairSync:

NonfairSync.lock()

final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

首先通過CAS嘗試將AQS的state由0變為1,如果成功,說明當前鎖沒有被線程持有,調用setExclusiveOwnerThread()設置當前線程持有當前鎖即可;

如果失敗了說明當前鎖被持有,調用acquire(1);

獲得()

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

在acquire()中,首先調用了tryAcquire()嘗試獲取

NonFairSync的tryAcquire的實現

final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }

首先判斷AQS的state是否為0,如果是0,則當前鎖未被持有,設置當前線程即可,如果不為0,說明當前鎖被持有,並且有另一個線程嘗試進入,則將AQS的state+1(類似synchronized的monitor的進入數);

addWaiter()

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

addWaiter的作用是將為獲得鎖被阻塞的線程打包成Node添加到tail鍊錶(隊列)中保存起來,添加鍊錶節點的過程使用了CAS添加;

Java-技術專題-AQS和Volatile和Synchronized實現原理 1

UnFairSync鎖定

回顧一下UnFairSync的lock()過程:首先嘗試通過CAS將鎖的狀態(AQS的state)由0變為1;如果成功說明鎖未被持有,設置當前線程持有即可,如果失敗,說明鎖已經被持有,調用acquire(1);在acquire()中,1首先調用tryAcquire()再次嘗試獲取鎖,如果失敗將鎖的state+1,2其次調用addWaiter()將當前線程包裝成Node放入等待隊列AQS的tail中,3調用當線程的interrupt()嘗試中斷;

開鎖():

public void unlock() {
sync.release(1);
}

public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

unLock()做的事情:將鎖的state-1,如果state==0了,在等待隊列中喚醒一個線程;

公平鎖和不公平鎖的區別:

FairLock.lock()

final void lock() {
acquire(1);
}

公平鎖的lock方法是直接調用acquire();

tryAcquire的實現:

/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }

在調用tryAcquire()嘗試獲取鎖時,公平和不公平鎖的實現稍微不同,公平鎖會在state == 0的時候直接設置當前線程去持有鎖,而非公平鎖會調用hasQueuedPredecessors()去判斷tail等待隊列中有沒有比當前線程等待時間更長的線程,如果有,就不會設置當前線程去持有鎖;

AQS內部的CLH自旋鎖

CLH是一個基於鍊錶(隊列)的自旋(公平)鎖,每一個等待鎖的線程封裝成節點,不斷自旋判斷前一個節點的狀態,如果前一個節點釋放鎖就結束自旋;

AQS的CLH隊列tail對CLH自旋鎖進行了兩個方面改進:

節點的結構:AQS中的CLH隊列的節點採用雙向鍊錶節點等待機制:傳統的CLH是通過不斷自旋判斷前一個節點的狀態,AQS改成了自旋+阻塞+喚醒,線程在經過幾次自旋後會進入阻塞狀態等待喚醒,喚醒後繼續自旋,等待前一個線程釋放鎖,兼顧了

性能和效率;

可重入鎖的實現:

可重入鎖:指任意線程在獲取到鎖之後能夠再次獲取該鎖而不會被鎖所阻塞

在tryAcquire()中判斷是否是當前線程持有,如果是則將state通過CAS +1;在釋放鎖時,只有state為0時,鎖才會正真釋放,可以被其他線程持有;

JMM(JAVA內存模型)

JVM定義了JMM用來屏蔽各種硬件和操作系統的內存訪問差異,即JMM的主要目標定義程序中各個變量訪問規則;

Java-技術專題-AQS和Volatile和Synchronized實現原理 2

JMM規定了所有的變量都存放在主內存,每個線程有自己的工作內存,工作內存中保存著主內存變量的副本,線程對變量的訪問只能通過自己的工作內存的副本訪問,每個線程只能訪問自己的工作線程;

內存間交互操作:

JMM定義了8種原子操作用來實現主內存到工作內存的拷貝,工作內存到主內存的同步:

lock鎖定:作用於主內存,將主內存的一個變量標識為一個線程獨占unlock解鎖:作用於主內存,把主內存中一個處於被線程獨占的變量釋放出來

read讀取:作用於主內存,把主內存的變量傳入到工作內存,配合load使用load載入:作用於工作內存,配合read將主內存的值放入工作內存的副本中

use使用:作用於工作內存,當虛擬機遇到一個需要使用這個變量的字節碼指令時,將工作內存變量的值傳遞給執行引擎。 assign賦值:作用於工作內存,當虛擬機遇到一個需要給變量賦值的字節碼指令時,從執行引擎接收新的值傳入工作內存。

store存儲:作用於工作內存,將工作內存的一個變量傳遞給主內存,配合write使用write寫入:作用於主內存,配合store將工作內存副本的值在主內存更新

揮發性:

被volatile修飾的變量具有可見性,有序性,保證單個變量的讀寫的原子性(i++不保證)

volatile原理:

被volatile修飾的變量會存在一個lock前綴,lock前綴的作用是將當前線程的工作內存中副本值直接寫入主內存,並且將其他工作內存的值失效(立刻執行store,write操作);強制刷新變量,保證可見性;lock前綴還有一個功能:內存屏障(重排序不能在內存屏障前執行),抑制重排序,保證有序性;

happen-before:先行發生原則

如果操作1 happen-before 操作2,那麼操作1的結果對操作2 是可見的僅要求可見性,不要求有序性,可以重排序;

volatile規定了變量的寫happen-before 變量的讀;

synchronized原理:

每一個對像都會有一個monitor,monitor是由C++實現的一個ObjectMonitor類,可以理解為一個實現線程同步的對象;

Java-技術專題-AQS和Volatile和Synchronized實現原理 3

Monitor的屬性

當多個線程同時訪問同步代碼塊時,會先將線程放入EntryList中,當一個線程持有monitor對象

後,會count++,設置owner為此線程;

如果owner調用wait()或者同步任務完成,就會將count--,owner設置為null,並且將這個線程

放入WaitSet,等待下一次被喚醒

監視者:

遇到monitorenter指令會嘗試獲取monitor對象,通過判斷monitor對象的count是否為0,如果count = 0,當前線程就持有鎖,count++,如果不會零,就阻塞,如果持有鎖的線程重新進入鎖,count繼續++;

監控退出:

執行exit指令的線程必須是鎖的持有者,執行完exit後,monitor的count--,如果count == 0,則退出鎖,被阻塞的線程可以嘗試獲取鎖; 同步代碼塊時通過enter/ exit字節碼指令實現的,如果是同步方法,就會在方法的字節碼加入一個ACC_SYNCHRONIZED的flag,如果有這個flag,表明這是一個同步方法,線程在執行時會嘗試獲取Monitor對象(靜態方法是類的Monitor,非靜態方法則是實例對象的Monitor),本質上和enter、exit一樣都是通過Monitor對象來實現的;

synchronized優化:

在早期的版本中,使用synchronized關鍵字加鎖都會將拿不到鎖的線程進行阻塞,需要上下文切換,效率較低,在jdk1.6以後對synchronized鎖進行了優化;

鎖消除:在代碼上加了鎖,但是虛擬機判斷出這一塊代碼不可能被多線程競爭,就會把這個鎖消除掉;虛擬機判斷的依據是逃逸分析

逃逸分析:

分析對象的動態作用域,當一個對像在方法中被定義後,它可能被外部其他方法訪問(作為參數傳入),這種稱為方法逃逸,如果被其他線程訪問,稱為線程逃逸,如果能證明這個對像不會逃逸到其他方法或者線程,虛擬機會對它做一些優化:

棧上分配:對象的內存存放在棧幀中而不是堆上;同步消除:鎖消除標量替換: 標量:一個數據無法分解成更小的數據,比如java原始類型;聚合量:可以被分解成更小的數據,比如對象;如果逃逸分析證明這個對像不會方法逃逸,並且這個對像是聚合量,那麼程序執行的時候可能不會創建這個對象,而是創建它的成員變量;

鎖粗化:如果虛擬機檢測到一串操作都對一個對象加鎖,釋放鎖,將會把加鎖的範圍粗化到整個操作的外部;

StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append("加锁----释放锁");
stringBuffer.append("加锁----释放锁");
stringBuffer.append("加锁----释放锁");

@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}

上面的操作就會進行synchronized鎖粗化的優化

自適應自旋:自旋時間由前一次在同一個鎖的自旋時間和鎖的擁有者狀態來決定,如果虛擬機判斷獲得這個鎖的可能性很大,就會增加自旋時間,如果覺得很難獲得鎖,可能會省去自旋這一步節約CPU;偏向鎖:這個鎖會偏向於第一個持有它的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,減少加鎖/解鎖的一些CAS操作(比如等待隊列的一些CAS操作),這種情況下,就會給線程加一個偏向鎖。 如果在運行過程中,遇到了其他線程搶占鎖,則持有​​偏向鎖的線程會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖;輕量級鎖:由偏向鎖轉化來,相對於傳統的重量級鎖,不會阻塞線程,而是通過自旋進行等待;以CPU為代價,避免線程的上下文切換,追求響應速度;偏向鎖切換到輕量級鎖會Stop the World

Java-技術專題-AQS和Volatile和Synchronized實現原理 4