java多線程基礎(chǔ)(volatile、synchronized、Lock)

java多線程基礎(chǔ)(volatile、synchronized、Lock)

java多線程安全性問題簡單分析

并發(fā)編程模型的兩個(gè)關(guān)鍵問題:
1.線程間的通信: java線程間通信是通過共享變量來解決的首懈,所以我們主要解決的是java內(nèi)存可見性問題
2.同步:同步是指程序中用于控制不同線程間操作發(fā)生相對順序的機(jī)制。
下面分析一下問題原因和在java中出現(xiàn)這兩個(gè)問題的解決方案谨敛。
java解決多線程內(nèi)存可見性問題
  • 問題原因分析: 在cpu工作的中究履,每個(gè)線程都會(huì)一個(gè)獨(dú)立的緩存(就CPU的一級緩存二級緩存),而cpu為了提高工作效率脸狸,在運(yùn)算中只操作緩存中的數(shù)據(jù)最仑,之后在異步的刷到主內(nèi)存中(jvm)。如下圖炊甲,線程A修改了某個(gè)全局變量泥彤,它只能保證線程A緩存中的變量值是正確的,線程A緩存的變量值什么時(shí)候刷新到主內(nèi)存中這個(gè)是不確定的卿啡,如果線程A修改了變量值沒有及時(shí)刷新道主內(nèi)存中吟吝,線程B正好用到線程A修改的變量,這個(gè)時(shí)候線程B緩存的變量值就是錯(cuò)誤的颈娜。
image.png
如下代碼:
    public class Test {
        private volatile static int i = 0;
        public static void main(String[] args) throws InterruptedException {
            new Thread() {
                @Override
                public void run() {
                    while (i == 0){

                    }
                }
            }.start();

            Thread.sleep(1000);
            i = 1;
        }

    }

上面代碼:我們把main線程當(dāng)做圖中A線程剑逃,new Thread().start(); 當(dāng)做圖中的B線程浙宜,i是全局變量。運(yùn)行結(jié)果B線程會(huì)線圖死循環(huán)中蛹磺,因?yàn)锳線程修改了i,沒有刷新到主內(nèi)存中所以B線程看到的i一直是0粟瞬。

  • 內(nèi)存可見性問題解決方案:
    1.變量增加volatile
    2.CAS操作變量,本質(zhì)也是用到的volatile(原子類:AtomicInteger萤捆、AtomicLong裙品、Atomic*...)
    3.synchronized,到這我們都會(huì)有個(gè)疑問synchronized是jvm級別的一個(gè)鎖,為什么鎖會(huì)解決內(nèi)存可見性問題鳖轰,等我們分析JMM的時(shí)候就清楚了清酥。
java解決多線程同步問題
  • 問題現(xiàn)狀: 在系統(tǒng)并行執(zhí)行時(shí)經(jīng)常會(huì)遇見某一個(gè)操作或者一組操作是線程之間互斥的扶镀,例如:隊(duì)列的入列和出列蕴侣、棧的壓棧 出棧等。這時(shí)就需要用到j(luò)ava的鎖來實(shí)現(xiàn)同步功能臭觉。
  • 同步問題解決方案:~~~~
    1.synchronized 鎖實(shí)現(xiàn)同步 例子:Hashtable昆雀、Vector、StringBuffer
    2.Lock 鎖實(shí)現(xiàn)同步 例子: jdk1.7 ConcurrentHashMap

volatile

volatile簡介
volatile主要作用是解決全局變量多線程之間可見性問題蝠筑,在多線程中一個(gè)變?nèi)至勘籿olatile修飾了狞膘,一個(gè)線程修改了該變量,其它線程會(huì)立刻感知到什乙。
volatile實(shí)現(xiàn)原理

java代碼:

    instance = new Singleton(); // instance 是 volatile變量 

轉(zhuǎn)變成匯編代碼:

0x01a3de1d: movb $0×0,0×1104800(%esi);
0x01a3de24: lock addl $0×0,(%esp);

我們可以看到有volatile修飾的變量進(jìn)行寫的時(shí)候會(huì)多出第二行匯編代碼挽封。我們說一下這個(gè)第二行代碼lock都干了什么事:
1.Lock前綴指令會(huì)引起處理器緩存會(huì)寫到內(nèi)存
2.一個(gè)處理器的緩存會(huì)寫到內(nèi)存會(huì)導(dǎo)致其他處理的緩存無效,CPU使用嗅探技術(shù)保證它的內(nèi)部緩存臣镣、系統(tǒng)內(nèi)存和其他處理器的緩存的數(shù)據(jù)在總線上保持一致辅愿。


synchronized

synchronized簡介
synchronized,jvm實(shí)現(xiàn)的同步鎖忆某,當(dāng)一個(gè)線程執(zhí)行到synchronized的時(shí)点待,需要先獲取到鎖,退出或異常時(shí)會(huì)釋放鎖弃舒。
synchronized三種形態(tài)
  • 對于普通同步方法癞埠,鎖的是當(dāng)前實(shí)例類對象
    
  • 對于靜態(tài)同步方法,鎖的是當(dāng)前類的Class對象
    
  • 對于同步方法快聋呢,所得是synchronized括號里面配置的對象苗踪。
    
synchronized實(shí)現(xiàn)原理
JVM基于進(jìn)入和退出monitor對象來實(shí)現(xiàn)方法同步和代碼塊同步,但是兩者實(shí)現(xiàn)的細(xì)節(jié)不一線削锰,代碼塊同步是使用monitorrenters和
monitorexit指令實(shí)現(xiàn)的徒探,方法同步是ACC_SYNCHRONIZED標(biāo)示實(shí)現(xiàn)的。下面我們看一下synchronized實(shí)現(xiàn)的同步的代碼喂窟。

java代碼:

public class Test {

   public synchronized void fun1(){
       int i = 1;
   }

   public void fun2(){

       synchronized (this){
           try{
               int i = 1;
           }catch (Exception e){

           }
       }

   }
}

上述java代碼 fun1方法是synchronized方法同步的例子测暗,fun2是synchronized代碼塊同步的例子央串。我們把這個(gè)類變異成class,看一看這個(gè)類的字節(jié)碼信息碗啄。

public io.netty.example.Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lio/netty/example/Test;

  public synchronized void fun1();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=1, locals=2, args_size=1
         0: iconst_1
         1: istore_1
         2: return
      LineNumberTable:
        line 6: 0
        line 7: 2
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       3     0  this   Lio/netty/example/Test;
            2       1     1     i   I

  public void fun2();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: iconst_1
         5: istore_2
         6: goto          10
         9: astore_2
        10: aload_1
        11: monitorexit
        12: goto          20
        15: astore_3
        16: aload_1
        17: monitorexit
        18: aload_3
        19: athrow
        20: return
      Exception table:
         from    to  target type
             4     6     9   Class java/lang/Exception
             4    12    15   any
            15    18    15   any
      LineNumberTable:
        line 11: 0
        line 13: 4
        line 16: 6
        line 14: 9
        line 17: 10
        line 19: 20
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      21     0  this   Lio/netty/example/Test;
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 9
          locals = [ class io/netty/example/Test, class java/lang/Object ]
          stack = [ class java/lang/Exception ]
        frame_type = 0 /* same */
        frame_type = 68 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4

在上面class文件中我們可以看出來
fun1方法同步在flags后面有個(gè)標(biāo)志ACC_SYNCHRONIZED來描述此方法是同步方法需要現(xiàn)獲取鎖质和。
fun2方法中我們我可看到在CODE 3 , 11 , 17 處,有個(gè)monitorenter稚字,monitorexit 饲宿,分別對應(yīng) monitorenter 獲取鎖,11 monitorexit正常退出釋放鎖胆描,17 monitorexit 異常退出釋放鎖瘫想。

通過上面我們知道了synchronized 方法同步是通過ACC_SYNCHRONIZED 實(shí)現(xiàn)獲取鎖和釋放鎖,代碼塊同步是通過 monitorenter昌讲,monitorexit兩個(gè)指令實(shí)現(xiàn)獲取鎖和釋放鎖国夜。

synchronized的鎖是存在java對象頭里的。我們來看一下java對象頭mark word的結(jié)構(gòu)短绸。

32位虛擬機(jī):

image.png

64位虛擬機(jī):

image.png

鎖標(biāo)志位:
01:偏向鎖或者無鎖
00:輕量級鎖
10:重量級鎖
11:GC標(biāo)記
是否是偏向鎖
0:否
1:是

java對象頭mark word的狀態(tài)變化:
無鎖狀態(tài):

image.png

偏向鎖:


image.png

輕量鎖:


image.png

重量鎖:


image.png

被GC標(biāo)記過的mark word:


image.png

到這我們就應(yīng)該明白了车吹,synchronized是怎么控制鎖的,所有機(jī)關(guān)都是對象mark word中醋闭。判斷對象是否有鎖先查看mark word里面的信息窄驹。 獲取鎖和釋放鎖都會(huì)修改mark word 信息。

synchronized鎖升級
  • 偏向鎖
    簡單的講就是在對象頭上記錄threadId, 第一次獲取鎖如果將NULL替換成當(dāng)前線程的threadId证逻,下次在獲取鎖的時(shí)候發(fā)現(xiàn)threadid是一樣的就可以直接認(rèn)為當(dāng)前線程獲取到了鎖乐埠,忽略了輕量鎖和重量鎖提高了加鎖效率。偏向鎖使用了一種等到競爭出現(xiàn)才釋放鎖的機(jī)制囚企,所以其他線程嘗試競爭偏向鎖是丈咐,持有偏向鎖的線程才會(huì)釋放。偏向鎖的撤銷洞拨,需要等待全局安全點(diǎn)(線程暫停,在這個(gè)時(shí)間點(diǎn)上沒有正在執(zhí)行的字節(jié)碼)扯罐。
    偏向鎖獲取和撤銷流程


    image.png
  • 輕量鎖、重量級鎖
    輕量鎖:輕量鎖是通過CAS把Mark Word更新成指向棧中所記錄指針(有心的可以看一下C++的代碼 烦衣, 獲取鎖的時(shí)候會(huì)創(chuàng)建一個(gè)BasicLock的對象歹河,Mark Word 記錄的就是這個(gè)對象的指針),所標(biāo)記更新成00花吟,更新失敗會(huì)有一個(gè)自旋的過程秸歧,通過-XX:PreBlockSpin=10來設(shè)置自旋次數(shù),默認(rèn)是10次衅澈。
    重量鎖:重量級鎖是把Mark Word更新成monitor對象指針键菱。通過monitor對象來維護(hù)鎖的等待和獲取。

    monitor對象關(guān)鍵屬性:
    _recursions: 重入鎖的次數(shù)
    _owner: 當(dāng)前持有所得線程
    _WaitSet:等待線程組成的雙向循環(huán)鏈表今布,_WaitSet是第一個(gè)節(jié)點(diǎn)
    _cxq: 多線程競爭鎖進(jìn)入時(shí)的單向鏈表
    _EntryList: _owner從該雙向循環(huán)鏈表中喚醒線程結(jié)點(diǎn)经备,_EntryList是第一個(gè)節(jié)點(diǎn)
    java底層維護(hù)了一個(gè)兩個(gè)隊(duì)列一個(gè)是待獲取鎖隊(duì)列一個(gè)是等待隊(duì)列拭抬,_owner屬性表示當(dāng)前持有鎖的線程。誰先占有_owner屬性誰就獲取到線程鎖侵蒙,隊(duì)列響應(yīng)就會(huì)移出去一個(gè)節(jié)點(diǎn)造虎。

    鎖膨脹流程圖:

image.png
  • 偏向鎖、輕量鎖纷闺、重量鎖升級過程

    來自網(wǎng)上的一張圖講述了偏向鎖到輕量鎖再到重量鎖全部過程算凿, 很NB分享一下。


    image.png

Lock

Lock簡介
synchronized是JVM實(shí)現(xiàn)的鎖犁功,Lock是JDK實(shí)現(xiàn)的鎖氓轰,提供了雨synchronized關(guān)鍵字類似的功能,只是需要顯示的獲取或和釋放鎖浸卦。雖然Lock鎖少了隱式獲取鎖釋放鎖的便捷性署鸡,但是卻擁有獲取鎖與釋放鎖的操作性、可中斷性已經(jīng)超時(shí)獲取鎖等多種镐躲。Lock支持ReentranLock可重入鎖储玫,ReentrantReadWriteLock可重入讀寫鎖侍筛,以上兩個(gè)鎖都支持公平非公平兩種模式萤皂。
Lock接口Api
image.png
Lock實(shí)現(xiàn)原理
AQS 簡介
Lock鎖實(shí)現(xiàn)主要依賴的是AbstarctQueuedSynchronized(簡稱AQS)同步器是實(shí)現(xiàn)的鎖的一系列功能,我們來分析一下AQS的主要代碼匣椰。
AQS->方法概述

可重寫方法:


image.png

模板方法:


image.png
AQS->關(guān)鍵屬性

/* 等待隊(duì)列鏈表頭節(jié)點(diǎn) AQS用的雙向鏈表做的FIFO隊(duì)列 /
private transient volatile Node head;
/
等待隊(duì)列鏈表尾節(jié)點(diǎn) */
private transient volatile Node tail;

/* 同步狀態(tài): 鎖沒有被線程獲取時(shí)是0 */
private volatile int state;

AQS->Node屬性介紹
 final class Node {
        /** 標(biāo)記共享節(jié)點(diǎn) */
        static final Node SHARED = new Node();
        /** 標(biāo)記獨(dú)占節(jié)點(diǎn) */
        static final Node EXCLUSIVE = null;
        /**
         * 節(jié)點(diǎn)等待狀態(tài)
         *
         * CANCELLED =  1;
         * 由于隊(duì)列中的等待的線程等待超時(shí)或者中斷裆熙,需要從同步隊(duì)列中取消等待,節(jié)點(diǎn)進(jìn)入該狀態(tài)不會(huì)變化
         *
         * SIGNAL    = -1;
         * 后續(xù)幾點(diǎn)處于等待狀態(tài)禽笑,而當(dāng)前節(jié)點(diǎn)的線程如果釋放了同步狀態(tài)或者被取消入录,將會(huì)通知后續(xù)節(jié)點(diǎn),是后續(xù)節(jié)點(diǎn)的線程得以運(yùn)行
         *
         * CONDITION = -2;
         * 節(jié)點(diǎn)在等待隊(duì)列中佳镜,節(jié)點(diǎn)線程在Condition上僚稿,當(dāng)其他線程對Condition調(diào)用了signal方法后,該節(jié)點(diǎn)將會(huì)從等待隊(duì)列中轉(zhuǎn)義到同步隊(duì)列中蟀伸,加入到對同步狀態(tài)的獲取中
         *
         * PROPAGATE = -3;
         * 表示下一次共享式同步狀態(tài)獲取將會(huì)無條件地傳播下去
         *
         * 0  初始狀態(tài)
         */
        volatile int waitStatus;

        /**
         * 隊(duì)列上一個(gè)節(jié)點(diǎn)
         */
        volatile Node prev;

        /**
         * 隊(duì)列下一個(gè)節(jié)點(diǎn)
         */
        volatile Node next;

        /**
         * 當(dāng)前獲取節(jié)點(diǎn)線程
         */
        volatile Thread thread;

        /**
         * 下一個(gè)等待節(jié)點(diǎn)
         */
        Node nextWaiter;

    }
獨(dú)占鎖
  • 獨(dú)占鎖狀態(tài)獲取
public final void acquire(int arg) {
        // tryAcquire 嘗試獲取鎖如果成功直接退出
        // addWaiter 創(chuàng)建節(jié)點(diǎn)并添加到隊(duì)列尾部(EXCLUSIVE 表示獨(dú)占節(jié)點(diǎn))
        // acquireQueued 采用死循環(huán)方式獲取同步狀態(tài)蚀同,為獲取到活執(zhí)行park線程阻塞
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

addWaiter創(chuàng)建等待節(jié)點(diǎn),添加至等待隊(duì)列尾部:

        /**
    * 節(jié)點(diǎn)添加至隊(duì)列尾部 mode: SHARED啊掏,EXCLUSIVE 共享獨(dú)占兩種方式
    */
    private Node addWaiter(Node mode) {
        // 創(chuàng)建節(jié)點(diǎn)對象
        Node node = new Node(Thread.currentThread(), mode);
        // tail 隊(duì)列尾節(jié)點(diǎn)
        Node pred = tail;
        
        if (pred != null) {
            // 新節(jié)點(diǎn)前序節(jié)點(diǎn)指向 tail節(jié)點(diǎn)
            node.prev = pred;
            // 通過CAS 方式替換尾部節(jié)點(diǎn)
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //  tail為空 或者 CAS 失敗循環(huán)添加節(jié)點(diǎn)到隊(duì)列尾部
        enq(node);
        return node;
    }

        /**
    * 循環(huán)通過CAS方式添加節(jié)點(diǎn)至隊(duì)列尾部蠢络,
    */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // tail 是空初始隊(duì)列
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                // 通過CAS 方式替換尾部節(jié)點(diǎn)
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

acquireQueued 獲取同步狀態(tài):

/**
    *   FIFO隊(duì)列獲取同步狀態(tài)
    */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 當(dāng)前節(jié)點(diǎn)的前序節(jié)點(diǎn)
                final Node p = node.predecessor();
                // 如果前序節(jié)點(diǎn)是頭節(jié)點(diǎn) 嘗試獲取同步狀態(tài)
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // shouldParkAfterFailedAcquire 通過waitStatus判斷線程是否需要阻塞  
                // parkAndCheckInterrupt 阻塞線程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 執(zhí)行了interrupt 終止線程,撤銷獲取同步狀態(tài)
            if (failed)
                cancelAcquire(node);
        }
    }
    
    /**
    * 通過waitStatus判斷線程是否需要阻塞
    */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // 前序節(jié)點(diǎn)是等待狀態(tài) 當(dāng)前節(jié)點(diǎn)返回阻塞
        if (ws == Node.SIGNAL)
            return true;
        // 前序節(jié)點(diǎn)放棄了獲取同步狀態(tài)繼續(xù)往前找節(jié)點(diǎn) 
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // CAS 替換節(jié)點(diǎn)為 等待狀態(tài)迟蜜, 如果成功阻塞線程
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
  • 獨(dú)占鎖釋放
/**
    * 釋放鎖
    */
    public final boolean release(int arg) {
        // 同步狀態(tài) 歸0
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                // 初始等待狀態(tài) 獲取下一個(gè)節(jié)點(diǎn) 并喚醒線程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    
    /**
    *   初始等待狀態(tài)刹孔、喚后序節(jié)點(diǎn)醒線程
    */
    private void unparkSuccessor(Node node) {
        
        int ws = node.waitStatus;
        // 等待狀態(tài)初始為0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        // 獲取下一個(gè)節(jié)點(diǎn)
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            // 喚醒阻塞線程
            LockSupport.unpark(s.thread);
    }
    
獲取共享鎖
  • 共享鎖狀態(tài)獲取
/**
    * 創(chuàng)建共享模式等待節(jié)點(diǎn) ,死循環(huán)獲取同步狀態(tài)娜睛,獲取失敗阻塞線程
    */
    private void doAcquireShared(int arg) {
        // 創(chuàng)建等待節(jié)點(diǎn) 獨(dú)占模式是一個(gè)方法
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // 當(dāng)前節(jié)點(diǎn)前序   節(jié)點(diǎn)
                final Node p = node.predecessor();
                if (p == head) {
                    // 嘗試獲取共享鎖 大于0 獲取成功
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        // 向后傳播共享模式同步狀態(tài)
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                // shouldParkAfterFailedAcquire 通過waitStatus判斷線程是否需要阻塞  
                // parkAndCheckInterrupt 阻塞線程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  • 共享模式同步狀態(tài)釋放
public final boolean releaseShared(int arg) {
        // 釋放同步狀態(tài)
        if (tryReleaseShared(arg)) {
            // 喚醒等待線程
            doReleaseShared();
            return true;
        }
        return false;
    }
    
    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 等待線程喚醒
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;   
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;
            }
            if (h == head)   
                break;
        }
    }
AQS->公平鎖髓霞、非公平鎖

ReentrantLock來說公平鎖和非公平鎖卦睹。
公平鎖:公平性與否是針對獲取鎖而言的,如果一個(gè)鎖是公平的方库,那么鎖的獲取順序就應(yīng)該符合請求的絕對時(shí)間順序分预,也就是FIFO。
優(yōu)點(diǎn):保證時(shí)間順序獲取鎖薪捍,鎖獲取相對公平笼痹,不會(huì)有線程饑餓問題 缺點(diǎn):效率低會(huì)頻繁的CPU上下文切換
非公平鎖: 只要CAS成功就獲取到鎖,不按照時(shí)間順序獲取說
有點(diǎn):效率高 缺點(diǎn):會(huì)產(chǎn)生線程饑餓

公平鎖獲取源碼分析:

/**
    * 公平鎖
    */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            // 直接執(zhí)行AQS獲取同步狀態(tài)方法酪穿、嚴(yán)格按照FIFO順序獲取鎖
            acquire(1);
        }

        
        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;
                }
            }
            // 可重入鎖 如果當(dāng)前線程和以獲取鎖的線程是一個(gè) 可以再次獲取鎖
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

非公平鎖源碼分析:

/**
    * 非公平鎖
    */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

       
        final void lock() {
            // 先更新一個(gè)同步鎖的狀態(tài)凳干,成功就獲取到鎖,不成功在行AQS獲取鎖的方法
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
    
    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;
        }
重入鎖

ReentrantLock 可重入鎖,用的AQS獨(dú)占鎖被济。

讀寫鎖

ReentrantReadWriteLock 可重入讀寫鎖救赐。讀寫鎖和可重入鎖不同的是:
1.寫鎖用的是AQS的獨(dú)占鎖,讀鎖用AQS的共享鎖只磷;
2.鎖狀態(tài)區(qū)分讀寫鎖:高16位表示讀鎖经磅, 低16位表示寫鎖


image.png

源碼分析一下讀鎖的實(shí)現(xiàn),寫鎖是低16位標(biāo)示的實(shí)現(xiàn)和上面的可重入鎖基本一致钮追。我們只分析鎖的獲取即可:

/**
        * 嘗試獲取讀鎖
        */
        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            /**
            *exclusiveCount (c & ((1 << SHARED_SHIFT) - 1)) 返回非0標(biāo)示已經(jīng)有寫鎖占用  
            *getExclusiveOwnerThread() != current 判斷已獲取讀鎖的線程和當(dāng)前線程不是一個(gè)線程 
            *返回-1 獲取鎖失敗
            */ 
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            // 鎖共享次數(shù)    
            int r = sharedCount(c);
            // readerShouldBlock 
            //  公平鎖下:判斷當(dāng)前線程是否需要被阻塞 预厌,如果隊(duì)列中有等待返回true 
            //  非公平鎖下: 判斷如果head 下一個(gè)節(jié)點(diǎn)是寫鎖, 先讓寫鎖執(zhí)行 避免寫鎖饑餓
            if (!readerShouldBlock() &&
            // 必須要小于最大獲取鎖次數(shù)
                r < MAX_COUNT &&
                // CAS 占用鎖狀態(tài) 
                compareAndSetState(c, c + SHARED_UNIT)) {
                
                // 到這了就標(biāo)示已經(jīng)獲取到鎖了
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
                } else {
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            
            // 循環(huán)獲取鎖 
            return fullTryAcquireShared(current);
        }
Condition源碼分析
Api

Condition 提供了類似Object的wait/notify的方法和Lock配合使用


image.png
await線程等待源碼分析

ConditionObject 類屬性分析:

// 第一個(gè)等待節(jié)點(diǎn)
private transient Node firstWaiter;
/** 最末尾等待節(jié)點(diǎn) */
private transient Node lastWaiter;

/** 可以看出來Condition,用鏈表做了一個(gè)等待對了元媚。 */

await方法源碼

/**
        * 持有LOCK線程進(jìn)入阻塞狀態(tài) 并釋放鎖
        */
        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            // 創(chuàng)建等待節(jié)點(diǎn)
            Node node = addConditionWaiter();
            // 釋放同步狀態(tài) 并返回原同步狀態(tài)值
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // isOnSyncQueue 節(jié)點(diǎn)是否在同步隊(duì)列中 在同步隊(duì)列中的不能 wait
            while (!isOnSyncQueue(node)) {
                // 線程阻塞
                LockSupport.park(this);
                // 線程恢復(fù) 進(jìn)入同步隊(duì)列的帶獲取同步狀態(tài)
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // acquireQueued 獲取同步狀態(tài)
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // 
                // 清除等待隊(duì)列的垃圾節(jié)點(diǎn)
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
signal線程等待源碼分析

signal 源碼:

public final void signal() {
            // 當(dāng)前線程沒有鎖 則拋出異常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                // 真正的喚醒線程
                doSignal(first);
        }
        
        
        private void doSignal(Node first) {
            do {
                //清除第一個(gè)等待節(jié)點(diǎn)
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
                // transferForSignal 真正真正的喚醒線程
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
        
        final boolean transferForSignal(Node node) {
        // 節(jié)點(diǎn)等待狀態(tài) 恢復(fù)初始狀態(tài)  
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        // 等待節(jié)點(diǎn)添加到同步隊(duì)列末尾轧叽,返回上一個(gè)等待節(jié)點(diǎn)
        Node p = enq(node);
        int ws = p.waitStatus;
        
        //ws > 0,同步狀態(tài)未占用刊棕, 可以立即恢復(fù)線程
        //上一個(gè)節(jié)點(diǎn) 狀態(tài)改成等待狀態(tài)炭晒,標(biāo)示當(dāng)前節(jié)點(diǎn)就是head節(jié)點(diǎn)可以立即恢復(fù)線程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            // 恢復(fù)線程
            LockSupport.unpark(node.thread);
        return true;
        }

signalall源碼:

private void doSignalAll(Node first) {
            // 等待隊(duì)列收尾都值為空
            lastWaiter = firstWaiter = null;
            do {
                // 循環(huán)每個(gè)等待節(jié)點(diǎn), 請求transferForSignal添加到等待隊(duì)列或者 直接恢復(fù)線程
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }

想整理的基本就這么多甥角,大部分是《java并發(fā)編程藝術(shù)》觀后整理的一些東西网严, 一少部分是自己帶著疑問去研究的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嗤无,一起剝皮案震驚了整個(gè)濱河市震束,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌翁巍,老刑警劉巖驴一,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異灶壶,居然都是意外死亡肝断,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來胸懈,“玉大人担扑,你說我怎么就攤上這事∪で” “怎么了涌献?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長首有。 經(jīng)常有香客問我燕垃,道長,這世上最難降的妖魔是什么井联? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任卜壕,我火速辦了婚禮,結(jié)果婚禮上烙常,老公的妹妹穿的比我還像新娘轴捎。我一直安慰自己,他們只是感情好蚕脏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布侦副。 她就那樣靜靜地躺著,像睡著了一般驼鞭。 火紅的嫁衣襯著肌膚如雪秦驯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天终议,我揣著相機(jī)與錄音汇竭,去河邊找鬼葱蝗。 笑死穴张,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的两曼。 我是一名探鬼主播皂甘,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼悼凑!你這毒婦竟也來了偿枕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤户辫,失蹤者是張志新(化名)和其女友劉穎渐夸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體渔欢,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡墓塌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苫幢。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡访诱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出韩肝,到底是詐尸還是另有隱情触菜,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布哀峻,位于F島的核電站涡相,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剩蟀。R本人自食惡果不足惜漾峡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望喻旷。 院中可真熱鬧生逸,春花似錦、人聲如沸且预。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锋谐。三九已至遍尺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涮拗,已是汗流浹背乾戏。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留三热,地道東北人鼓择。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像就漾,于是被迫代替她去往敵國和親呐能。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 夜鶯2517閱讀 127,720評論 1 9
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月抑堡,有人笑有人哭摆出,有人歡樂有人憂愁,有人驚喜有人失落首妖,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,536評論 28 53
  • 兔子雖然是枚小碩 但學(xué)校的碩士四人寢不夠 就被分到了博士樓里 兩人一間 在學(xué)校的最西邊 靠山 兔子的室友身體不好 ...
    待業(yè)的兔子閱讀 2,603評論 2 9
  • 信任包括信任自己和信任他人 很多時(shí)候偎漫,很多事情,失敗有缆、遺憾象踊、錯(cuò)過舌仍,源于不自信,不信任他人 覺得自己做不成通危,別人做不...
    吳氵晃閱讀 6,189評論 4 8