java多線程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析


java多線程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

前言:如有不正確的地方,還望指正。

目錄

Synchronized

原理

  • synchronized關鍵字是通過字節碼指令來實現的
  • synchronized關鍵字編譯后會在同步塊前后形成monitorenter和monitorexit兩個字節碼指令
  • 執行monitorenter指令時需要先獲得對象的鎖(每個對象有一個監視器鎖monitor),如果這個對象沒被鎖或者當前線程已經獲得此鎖(也就是重入鎖),那么鎖的計數器+1。如果獲取失敗,那么當前線程阻塞,直到鎖被對另一個線程釋放
  • 執行monitorexit指令時,計數器減一,當為0的時候鎖釋放
class Test
{
    public int i=1;
    public void test()
    {
	    synchronized (this)
	    {
	    	i++;	
	    }
}
}
  • 反編譯后結果

volatile

作用

  • 保證變量對所有的線程的可見性,當一個線程修改了這個變量的值,其他線程可以立即知道這個新值(之所以有可見性的問題,是因為java的內存模型)

原理

  • 所有變量都存在主內存,每條線程有自己的工作內存,工作內存保存了被該線程使用的變量的主內存副本拷貝
  • 線程對變量的所有操作都必須在工作內存中進行,不能直接讀寫主內存的變量,也就是必須先通過工作內存
  • 一個線程不能訪問另一個線程的工作內存
  • volatile保證了變量更新的時候能夠立即同步到主內存,使用變量的時候能立即從主內存刷新到工作內存,這樣就保證了變量的可見性
  • 實際上是通過內存屏障來實現的。語義上,內存屏障之前的所有寫操作都要寫入內存;內存屏障之后的讀操作都可以獲得同步屏障之前的寫操作的結果。

Atomic

作用

  • 當有多個線程同時對單個(包括基本類型及引用類型)變量進行操作時,具有排他性,即當多個線程同時對該變量的值進行更新時,僅有一個線程能成功,而未成功的線程可以像自旋鎖一樣,繼續嘗試,一直等到執行成功。

原理

  • CAS操作(compare and swap 對比和設置),是通過一個cpu指令實現的,這個指令是一個原子指令,指令有3個操作數ABC,A為內存位置,B為預期值,C為新值,如果A符合舊預期值B,那么用V更新A的值,如果不符合就不更新,這個過程是原子操作
  • 所以我們並沒有通過代碼來實現同步,而是通過硬件級別的cpu指令來實現的,並不像synchronized一樣阻塞線程
//加一並返回值
public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
   }

//返回CAS操作成功與否
public final boolean compareAndSet(int expect, int update) {
        //根據變量在內存中的偏移地址valueOffset獲取原值,然后和預期值except進行比,如果符合,用update值進行更新,這個過程是原子操作
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }


 
  • 如果此時有兩個線程,線程A得到current值為1,線程B得到current值也為2,此時線程A執行CAS操作,成功將值改為2,而此時線程B執行CAS操作,發現此時內存中的值並不是讀到current值1,所以返回false,此時線程B繼續進行循環,最后成功加1

Lock

作用

  • 顯式加鎖

原理

  • 通過同步器AQS(AbstractQueuedSynchronized類)來實現的,AQS根本上是通過一個雙向隊列來實現的
  • 線程構造成一個節點,一個線程先嘗試獲得鎖,如果獲取鎖失敗,就將該線程加到隊列尾部
  • 非公平鎖的lock方法,調用的sync(NonfairSync和fairSync的父類)的lock方法
 public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

// ReentrantLock的lock方法
public void lock() {
        sync.lock();
    }
  • NonfairSync的lock方法,acquire的是Sync的父類AQS的acquire方法

final void lock() {
            //如果當前同步狀態為0(鎖未被占有),CAS操作設置同步狀態,設置成功的話當前線程獲得鎖(如果此時是公平鎖,那么不會執行compareAndSetState方法,直接acuire排隊)
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            //否則調用AQS的acquire方法
            else
                acquire(1);
        }

 //CAS設置鎖的狀態
 protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }
  • AQS的acquire方法
//嘗試獲得鎖,如果獲取失敗,將節點加入到尾節點
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • tryAcquire方法,嘗試獲得鎖
final boolean nonfairTryAcquire(int acquires) {
    
    final Thread current = Thread.currentThread();
    //獲取state變量值
    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");
        // 更新state值為新的重入次數
        setState(nextc);
        return true;
    }
    //獲取鎖失敗
    return false;
}

  • 如果獲取鎖失敗,將節點加入尾節點
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;
                //此時可能同時有其他線程插入,再進行判斷(通過CAS),如果沒有,將節點設置為尾節點
             if (compareAndSetTail(pred, node)) {
                 pred.next = node;
                 return node;
             }
         }
            //如果節點為空或者節點不為空並且有其他線程插入(CAS返回false),執行enq方法
         enq(node);
         return node;
     }
  • 如果節點為空或者節點不為空並且有其他線程插入(CAS返回false),執行enq
//通過自旋進行設置
private Node More enq(final Node node) {
         for (;;) {
             Node t = tail;
             if (t == null) { // Must initialize
                 if (compareAndSetHead(new Node()))
                     tail = head;
             } else {
                 node.prev = t;
                 if (compareAndSetTail(t, node)) {
                     t.next = node;
                     return t;
                 }
             }
         }
     }
  • 進入隊列的線程嘗試獲得鎖

 
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true; //是否成功獲取鎖
    try {
        boolean interrupted = false; //線程是否被中斷過
        for (;;) {
            final Node p = node.predecessor(); //獲取前驅節點
            //如果前驅是head嘗試獲鎖
            if (p == head && tryAcquire(arg)) {
                setHead(node); // 獲取成功,將當前節點設置為head節點
                p.next = null; // 原head節點出隊
                failed = false; 
                return interrupted; //返回是否被中斷過
            }
            // 前節點不是頭節點或者獲取失敗,判斷是否可以掛起
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                // 線程若被中斷,設置interrupted為true
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}


  • 線程是否可以掛起

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //前驅節點的狀態
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 前驅節點狀態為signal(此節點線程結束后喚醒下一個節點線程)
        return true;
    //如果 前驅節點狀態為CANCELLED(線程已經被取消)
    if (ws > 0) {
        // 刪除cancelled狀態的節點
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 將前驅節點的狀態設置為SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
  • 掛起當前線程,返回線程中斷狀態並重置
 
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)

作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM