ReentrantLock(AQS),Volatile,Synchronized的實現(xiàn)原理

本文參考:

JUC學(xué)習(xí)(八):AQS的CLH隊列
并發(fā)編程——詳解 AQS CLH 鎖
JMM和底層實現(xiàn)原理

AQS

ReentrantLock類關(guān)于lock接口的操作都交給了內(nèi)部類Sync類來實現(xiàn),Sync類又有兩個子類NonFairSync,F(xiàn)airSync,公平鎖和不公平鎖;

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;    //  鎖的狀態(tài)

AQS使用的設(shè)計模式:

模板方法設(shè)計模式:定義一個代碼模板結(jié)構(gòu),相同部分在父類實現(xiàn),不同部分由子類實現(xiàn)
模板方法模式(Template Method) - 最易懂的設(shè)計模式解析

安卓中的View,Activity都使用了模板方法設(shè)計模式,View類規(guī)范了所有View需要實現(xiàn)的行為,View的子類可以在onMeasure,onLayout,onDraw中擴(kuò)展各自不同的行為;體現(xiàn)了設(shè)計模式的開閉原則
AQS抽象類為子類提供了tryAcquire,tryRelease去擴(kuò)展自己的不同行為

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變?yōu)?,如果成功,說明當(dāng)前鎖沒有被線程持有,調(diào)用setExclusiveOwnerThread()設(shè)置當(dāng)前線程持有當(dāng)前鎖即可;
如果失敗了說明當(dāng)前鎖被持有,調(diào)用acquire(1);

acquire()

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

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

NonFairSync的tryAcquire的實現(xiàn)

        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,則當(dāng)前鎖為被持有,設(shè)置當(dāng)前線程即可,如果不為0,說明當(dāng)前鎖被持有,并且有另一個線程嘗試進(jìn)入,則將AQS的state+1(類似synchronized的monitor的進(jìn)入數(shù));

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鏈表(隊列)中保存起來,添加鏈表節(jié)點的過程使用了CAS添加;


UnFairSync lock

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

unLock():

    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了,在等待隊列中喚醒一個線程;

公平鎖和不公平鎖的區(qū)別:

FairLock.lock()

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

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

tryAcquire的實現(xiàn):

        /**
         * 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;
        }

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

AQS內(nèi)部的CLH自旋鎖

CLH是一個基于鏈表(隊列)的自旋(公平)鎖,每一個等待鎖的線程封裝成節(jié)點,不斷自旋判斷前一個節(jié)點的狀態(tài),如果前一個節(jié)點釋放鎖就結(jié)束自旋;

AQS的CLH隊列tail對CLH自旋鎖進(jìn)行了兩個方面改進(jìn):

  • 節(jié)點的結(jié)構(gòu):AQS中的CLH隊列的節(jié)點采用雙向鏈表
  • 節(jié)點等待機(jī)制:傳統(tǒng)的CLH是通過不斷自旋判斷前一個節(jié)點的狀態(tài),AQS改成了自旋+阻塞+喚醒,線程在經(jīng)過幾次自旋后會進(jìn)入阻塞狀態(tài)等待喚醒,喚醒后繼續(xù)自旋,等待前一個線程釋放鎖,兼顧了性能和效率;

可重入鎖的實現(xiàn):

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

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

JMM JAVA Memory Model

JVM定義了JMM用來屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異,即JMM的主要目標(biāo)是定義程序中各個變量的訪問規(guī)則;


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

內(nèi)存間交互操作:

JMM定義了8種原子操作用來實現(xiàn)主內(nèi)存到工作內(nèi)存的拷貝,工作內(nèi)存到主內(nèi)存的同步:

  • lock 鎖定:作用于主內(nèi)存 ,將主內(nèi)存的一個變量標(biāo)識為一個線程獨(dú)占
  • unlock 解鎖:作用于主內(nèi)存,把主內(nèi)存中一個處于被線程獨(dú)占的變量釋放出來
  • read 讀取:作用于主內(nèi)存,把主內(nèi)存的變量傳入到工作內(nèi)存,配合load使用
  • load 載入:作用于工作內(nèi)存,配合read將主內(nèi)存的值放入工作內(nèi)存的副本中
  • use 使用:作用于工作內(nèi)存,當(dāng)虛擬機(jī)遇到一個需要使用這個變量的字節(jié)碼指令時,將工作內(nèi)存變量的值傳遞給執(zhí)行引擎
  • assign 賦值:作用于工作內(nèi)存,當(dāng)虛擬機(jī)遇到一個需要給變量賦值的字節(jié)碼指令時,從執(zhí)行引擎接收新的值傳入工作內(nèi)存
  • store 存儲:作用于工作內(nèi)存,將工作內(nèi)存的一個變量傳遞給主內(nèi)存,配合write使用
  • write 寫入:作用于主內(nèi)存,配合store將工作內(nèi)存副本的值在主內(nèi)存更新

Volatile:

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

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

happen-before:先行發(fā)生原則

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

  • volatile規(guī)定了變量的寫 happen-before 變量的讀;

Synchronized原理:

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


Monitor的屬性
  • 當(dāng)多個線程同時訪問同步代碼塊時,會先將線程放入EntryList中,當(dāng)一個線程持有monitor對象后,會count++,設(shè)置owner為此線程;

  • 如果owner調(diào)用wait()或者同步任務(wù)完成,就會將count--owner設(shè)置為null,并且將這個線程放入WaitSet,等待下一次被喚醒

monitorenter:

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

monitorexit:

執(zhí)行exit指令的線程必須是鎖的持有者,執(zhí)行完exit后,monitor的count--,如果count == 0,則退出鎖,被阻塞的線程可以嘗試獲取鎖;

同步代碼塊時通過enter/exit字節(jié)碼指令實現(xiàn)的,如果是同步方法,就會在方法的字節(jié)碼加入一個ACC_SYNCHRONIZED的flag,如果有這個flag,表明這是一個同步方法,線程在執(zhí)行時會嘗試獲取Monitor對象(靜態(tài)方法是類的Monitor,非靜態(tài)方法則是實例對象的Monitor),本質(zhì)上和enter、exit一樣都是通過Monitor對象來實現(xiàn)的;

synchronized優(yōu)化:

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

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

分析對象的動態(tài)作用域,當(dāng)一個對象在方法中被定義后,它可能被外部其他方法訪問(作為參數(shù)傳入),這種稱為方法逃逸,如果被其他線程訪問,稱為線程逃逸,如果能證明這個對象不會逃逸到其他方法或者線程,虛擬機(jī)會對它做一些優(yōu)化:

  1. 棧上分配:對象的內(nèi)存存放在棧幀中而不是堆上;
  2. 同步消除:鎖消除
  3. 標(biāo)量替換: 標(biāo)量:一個數(shù)據(jù)無法分解成更小的數(shù)據(jù),比如java原始類型;聚合量:可以被分解成更小的數(shù)據(jù),比如對象;如果逃逸分析證明這個對象不會方法逃逸,并且這個對象是聚合量,那么程序執(zhí)行的時候可能不會創(chuàng)建這個對象,而是創(chuàng)建它的成員變量;
  • 鎖粗化:如果虛擬機(jī)檢測到一串操作都對一個對象加鎖,釋放鎖,將會把加鎖的范圍粗化到整個操作的外部;
StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("加鎖----釋放鎖");
        stringBuffer.append("加鎖----釋放鎖");
        stringBuffer.append("加鎖----釋放鎖");

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

上面的操作就會進(jìn)行synchronized鎖粗化的優(yōu)化

  • 自適應(yīng)自旋:自旋時間由前一次在同一個鎖的自旋時間和鎖的擁有者狀態(tài)來決定,如果虛擬機(jī)判斷獲得這個鎖的可能性很大,就會增加自旋時間,如果覺得很難獲得鎖,可能會省去自旋這一步節(jié)約CPU;

  • 偏向鎖:這個鎖會偏向于第一個持有它的線程,如果在運(yùn)行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發(fā)同步的,減少加鎖/解鎖的一些CAS操作(比如等待隊列的一些CAS操作),這種情況下,就會給線程加一個偏向鎖。 如果在運(yùn)行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復(fù)到標(biāo)準(zhǔn)的輕量級鎖

  • 輕量級鎖:由偏向鎖轉(zhuǎn)化來,相對于傳統(tǒng)的重量級鎖,不會阻塞線程,而是通過自旋進(jìn)行等待;以CPU為代價,避免線程的上下文切換,追求響應(yīng)速度;偏向鎖切換到輕量級鎖會Stop the World

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容