synchronized實現原理及ReentrantLock原始碼

曹自標發表於2020-12-17

synchronized

synchronized的作用範圍
public class SynchronizedTest {
    // 例項方法,方法訪問標誌ACC_SYNCHRONIZED,鎖物件是物件例項
    public synchronized void test1(){}
    // 靜態方法,方法訪問標誌ACC_SYNCHRONIZED,鎖物件是MetaSpace中的Class
    // 相當於類的全域性鎖,會鎖住所有呼叫該方法的執行緒
    public synchronized static void test2(){}

    public void test3() {
        //同步程式碼塊,在程式碼塊前增加monitorenter指令,程式碼塊後增加monitorexit指令
        SynchronizedTest synchronizedTest = new SynchronizedTest();
        synchronized (synchronizedTest) {}
        // 類鎖,效果等同於鎖靜態方法。程式碼塊前後增加monitorenter、monitorexit指令
        synchronized (SynchronizedTest.class) {}
    }
}

可jclasslib檢視Acc_SYNCHRONIZED標誌和monitorenter、monitorexit指令

test1 方法:

Access flags: 0x0021[public synchronized]

test2 方法:

Access flags: 0x0029[public static synchronized]

test3方法Code操作碼:

 0 new #2 <com/java/study/jvm/SynchronizedTest>
 3 dup
 4 invokespecial #3 <com/java/study/jvm/SynchronizedTest.<init>>
 7 astore_1
 8 aload_1
 9 dup
10 astore_2
11 monitorenter
12 aload_2
13 monitorexit
14 goto 22 (+8)
17 astore_3
18 aload_2
19 monitorexit
20 aload_3
21 athrow
22 ldc #2 <com/java/study/jvm/SynchronizedTest>
24 dup
25 astore_2
26 monitorenter
27 aload_2
28 monitorexit
29 goto 39 (+10)
32 astore 4
34 aload_2
35 monitorexit
36 aload 4
38 athrow
39 return
synchronized實現

核心元件

  • Wait Set:哪些呼叫 wait方法被阻塞的執行緒被放置在這裡
  • Contention List: 競爭佇列,所有請求鎖的執行緒首先被放在這個競爭佇列中
  • Entry List: Contention List 中那些有資格成為候選資源的執行緒被移動到 Entry List 中
  • OnDeck:任意時刻, 最多隻有一個執行緒正在競爭鎖資源,該執行緒被成為 OnDeck
  • Owner:當前已經獲取到所資源的執行緒被稱為 Owner
  • !Owner:當前釋放鎖的執行緒

圖示過程:

解釋:

  1. JVM 每次從佇列的尾部取出一個資料用於鎖競爭候選者(OnDeck),但是併發情況下,ContentionList 會被大量的併發執行緒進行 CAS 訪問,為了降低對尾部元素的競爭, JVM 會將一部分執行緒移動到 EntryList 中作為候選競爭執行緒。
  2. Owner 執行緒會在 unlock 時,將 ContentionList 中的部分執行緒遷移到 EntryList 中,並指定EntryList 中的某個執行緒為 OnDeck 執行緒(一般是最先進去的那個執行緒)。
  3. Owner 執行緒並不直接把鎖傳遞給 OnDeck 執行緒,而是把鎖競爭的權利交給 OnDeck,OnDeck 需要重新競爭鎖。這樣雖然犧牲了一些公平性,但是能極大的提升系統的吞吐量,在JVM 中,也把這種選擇行為稱之為“競爭切換”。
  4. OnDeck 執行緒獲取到鎖資源後會變為 Owner 執行緒,而沒有得到鎖資源的仍然停留在 EntryList中。如果 Owner 執行緒被 wait 方法阻塞,則轉移到 WaitSet 佇列中,直到某個時刻通過 notify或者 notifyAll 喚醒,會重新進去 EntryList 中。
  5. 處於 ContentionList、 EntryList、 WaitSet 中的執行緒都處於阻塞狀態,該阻塞是由作業系統來完成的(Linux 核心下采用 pthread_mutex_lock 核心函式實現的)。
  6. Synchronized 是非公平鎖。 Synchronized 線上程進入 ContentionList 時, 等待的執行緒會先嚐試自旋獲取鎖,如果獲取不到就進入 ContentionList,這明顯對於已經進入佇列的執行緒是不公平的,還有一個不公平的事情就是自旋獲取鎖的執行緒還可能直接搶佔 OnDeck 執行緒的鎖資源。
    參考: https://blog.csdn.net/zqz_zqz/article/details/70233767
  7. 每個物件都有個 monitor 物件, 加鎖就是在競爭 monitor 物件,程式碼塊加鎖是在前後分別加上 monitorenter 和 monitorexit 指令來實現的,方法加鎖是通過一個標記位來判斷的
  8. synchronized 是一個重量級操作,需要呼叫作業系統相關介面,效能是低效的,有可能給執行緒加鎖消耗的時間比有用操作消耗的時間更多。
  9. Java1.6, synchronized 進行了很多的優化, 有適應自旋、鎖消除、鎖粗化、輕量級鎖及偏向鎖等,效率有了本質上的提高。在之後推出的 Java1.7 與 1.8 中,均對該關鍵字的實現機理做了優化。引入了偏向鎖和輕量級鎖。都是在物件頭中有標記位,不需要經過作業系統加鎖。
  10. 鎖可以從偏向鎖升級到輕量級鎖,再升級到重量級鎖。這種升級過程叫做鎖膨脹;
  11. JDK 1.6 中預設是開啟偏向鎖和輕量級鎖,可以通過-XX:-UseBiasedLocking 來禁用偏向鎖

ReentrantLock

ReentrantLock初始化時,會new一個同步類(預設非公平NonfairSync,當傳入公平引數fair=true時,則new公平類FairSync);而FairSync 和NonfairSync都繼承ReentrantLock中內部類Sync,Sync則繼承同步器AbstractQueuedSynchronizer。UML圖如下(https://www.cnblogs.com/zhimingyang/p/5702752.html 擷取):

Lock流程圖(非公平鎖示例)

原始碼
  1. ReentrantLock$NonfairSync#lock(),當state為0,即compareAndSetState(0, 1)為true時,獲得鎖;否則進行下一步
  2. ReentrantLock$NonfairSync#acquire() ——> AbstractQueuedSynchronizer#acquire() --> ReentrantLock$NonfairSync#tryAcquire() -->
    ReentrantLock$Sync#nonfairTryAcquire(), 第2次嘗試獲取鎖
  3. 在上面acquire方法中,還會呼叫addWaiter方法,將一個排他鎖加入佇列
public class ReentrantLock implements Lock, java.io.Serializable {
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //第2次嘗試獲取鎖
                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;
        }
    }
        
    static final class NonfairSync extends Sync {
    
        final void lock() {
            // 可不進入佇列,直接搶鎖
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
}
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    
    public final void acquire(int arg) {
        // 步驟3,加入等待佇列,預設排他鎖
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

而繼續addWaiter、enq和acquireQueued則是實現以下圖示過程:

    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;
            }
        }
        //前置節點為null的臨界條件,第一個執行緒進入等待佇列
        enq(node);
        return node;
    }

前置節點為null的臨界條件,第一個執行緒進入等待佇列,進行初始化

    private Node 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();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

node屬性值介紹:

對應原始碼:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    static final class Node {
        
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;

        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;

        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
}
重入鎖的實現

重入鎖的可重複進入在以下程式碼中實現(非公平鎖示例,公平鎖程式碼一樣):

  • c > 0, 即有鎖,並且獲取鎖的執行緒就是當前執行緒,則將state加1,並更新
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        ...
    }
    // c > 0
    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;
}
公平鎖和非公平鎖

第一處不公平地方(lock方法):

  • 非公平鎖lock時,如果發現沒有鎖了,即state為0,可以不管佇列,直接compareAndSetState,如果獲取true了(搶到鎖),直接獲得鎖,不用進同步器中的佇列。
  • 而公平鎖沒有此邏輯。
static final class NonfairSync extends Sync {

    final void lock() {
        // 可不進入佇列,直接搶鎖
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
}
static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }
}

第二處不公平的地方(tryAcquire):

  • 非公平鎖tryAcquire方法會呼叫Sync#nonfairTryAcquire(),當state為0,發現鎖被釋放時,可直接搶鎖
  • 公平鎖則必須滿足!hasQueuedPredecessors()條件,也即必須同步器中佇列沒有執行緒在等待,才去獲取鎖
static final class NonfairSync extends Sync {
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
abstract static class Sync extends AbstractQueuedSynchronizer {
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            //發現鎖被釋放時,可直接搶鎖
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        ...
    }
}

公平鎖

static final class FairSync extends Sync {
    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;
            }
        }
        ...
    }
}

第三處不公平地方,加入佇列時,前置節點是頭節點:

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                ...
                }
            }
    }

相關文章