死磕Java——ReentrantLock

讀書遛狗遠方發表於2019-05-09

一、死磕Java——ReentrantLock

ReentrantLockjava.util.concurrent.locks包下一個可重入的預設是非公平的鎖,ReentrantLock類是Lock介面的一個使用很頻繁的實現類,類結構如下圖:

image-20190508213733422

前面說過JMM模型要求的是可見性原子性有序性。解決原子性的方法也有多種,例如synchronized同步方法或者同步程式碼塊,也可以使用AtomicInteger原子包裝類解決,都知道synchronized加鎖是最笨重的解決方法,所裡,這裡使用的是ReentrantLock加鎖來實現原子性,程式碼如下:

class MyData {
    int num = 0;
    Lock lock = new ReentrantLock();
    public void add() {
        try {
            lock.lock();
            num++;
        } finally {
            lock.unlock();
        }
    }
}
public class ReentrantLockDemo {

    private static Logger log = LoggerFactory.getLogger(ReentrantLockDemo.class);

    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    myData.add();
                }
            }, String.valueOf(i)).start();
        }
        // 知道所有的執行緒執行結束
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        log.info("結果:{}", myData.num);
    }
}
複製程式碼

1.1.ReentrantLock的公平性

ReentrantLock提供了一個帶引數的建構函式,來讓使用者決定使用是否是公平鎖。

image-20190508215454860

通過原始碼我們可以知道,無引數就是預設為非公平鎖,傳入true表示公平鎖,傳入false表示非公平鎖,原始碼如下

// 空參預設為非公平鎖 
public ReentrantLock() {
     sync = new NonfairSync();
 }
複製程式碼
// 根絕引數決定鎖的公平性
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
複製程式碼

1.2.ReentrantLock的非公平鎖

1.2.1使用ReentrantLock加鎖

// 建立一個非公平鎖
Lock lock = new ReentrantLock();
try {
    // 加鎖
    lock.lock();
} finally {
    // 釋放鎖
    lock.unlock();
}
複製程式碼

1.2.2.執行的是ReentrantLock的lock方法

 public void lock() {
     sync.lock();
 }
複製程式碼

1.2.3.呼叫的是NonfairSync類的lock方法

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
    final void lock() {
        // 通過CAS思想去AQS佇列中獲取將State值從0變為1,即獲取到鎖
        if (compareAndSetState(0, 1))
            // 獲取鎖成功則將當前執行緒標記為持有鎖的執行緒,然後直接返回
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 獲取鎖失敗則執行該方法
            acquire(1);
    }
}
複製程式碼

1.2.4.呼叫AQS的cAS方法獲取鎖

protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    // unsafe類是通過native方法直接操作記憶體資料
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
複製程式碼

1.2.5.獲取鎖失敗執行acquire方法

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

1.2.6.tryAcquire嘗試獲取鎖

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
複製程式碼

tryAcquire這個方法是AQS預設的鉤子方法,不同類的有不同的實現,其中NonfairSync實現如下:

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
複製程式碼
 final boolean nonfairTryAcquire(int acquires) {
     // 傳入的acquires為1,獲取當前執行緒
     final Thread current = Thread.currentThread();
     // 獲取state變數的值,即當前鎖被重入的次數
     int c = getState();
     // state為0,說明當前鎖未被任何執行緒持有
     if (c == 0) {
         // 通過CAS思想去AQS佇列中獲取將State值從0變為1,即獲取到鎖
         if (compareAndSetState(0, acquires)) {
             // 獲取鎖成功則將當前執行緒標記為持有鎖的執行緒,然後直接返回
             setExclusiveOwnerThread(current);
             // 返回嘗試獲取鎖成功
             return true;
         }
     }
     // //當前執行緒就是持有鎖的執行緒,說明該鎖被重入了
     else if (current == getExclusiveOwnerThread()) {
         // //計算state變數要更新的值
         int nextc = c + acquires;
         if (nextc < 0) // overflow
             throw new Error("Maximum lock count exceeded");
         // //非同步方式更新state值
         setState(nextc);
         // 獲取鎖成功,返回結果
         return true;
     }
     // 嘗試獲取鎖失敗
     return false;
 }
複製程式碼

1.2.7.獲取鎖失敗後將執行緒加入到同步佇列中

private Node addWaiter(Node mode) {
    // 創造一個新的節點,傳入的mode引數為null
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    // tail是指向佇列尾元素的尾指標,新節點的頭指標指向佇列的尾指標
    Node pred = tail;
    // //佇列不為空
    if (pred != null) {
        // 新節點的頭指標修改為佇列的尾指標
        node.prev = pred;
        // 使用CAS演算法,如果記憶體中的佇列還是之前的尾指標就把新節點指向尾指標
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 安全的加入同步佇列
    enq(node);
    return node;
}
複製程式碼

1.2.8.將執行緒加入同步佇列

private Node enq(final Node node) {
    for (;;) {
        // t節點指向當前佇列的最後一個節點
        Node t = tail;
        // 佇列為空
        if (t == null) { // Must initialize
            // 通過CAS構造新節點
            if (compareAndSetHead(new Node()))
                // 尾指標指向新節點
                tail = head;
        } else {
            // 佇列不為空時候,將節點的頭指標指向佇列的尾指標
            node.prev = t;
            // 使用CAS演算法,如果記憶體中的佇列還是之前的尾指標就把新節點指向尾指標
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
複製程式碼

當佇列為空的時候通過CAS更新頭節點原始碼如下:

 private final boolean compareAndSetHead(Node update) {
     return unsafe.compareAndSwapObject(this, headOffset, null, update);
 }
複製程式碼

說明:僅當佇列中原值為null時更新成功。

當佇列不為空的時候通過CAS更新尾節點原始碼如下:

 private final boolean compareAndSetTail(Node expect, Node update) {
     return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
 }
複製程式碼

說明:CAS方式更新tial指標,僅當原值為t時更新成功

1.2.9.執行緒進入佇列後

 final boolean acquireQueued(final Node node, int arg) {
     // 引數arg為1,節點是為獲取到鎖的執行緒節點
     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);
     }
 }
複製程式碼

1.3.ReentrantLock加鎖總結

1.4.ReentrantLock非公平解鎖

lock.unlock();
複製程式碼

1.4.1.釋放鎖

public void unlock() {
	sync.release(1);
}
複製程式碼
public final boolean release(int arg) {
    // 釋放鎖(state-1),若釋放後鎖可被其他執行緒獲取(state=0),返回true
    if (tryRelease(arg)) {
        // 獲取佇列的頭節點
        Node h = head;
        //當前佇列不為空且頭結點狀態不為初始化狀態(0)  
        if (h != null && h.waitStatus != 0)
            // 喚醒同步佇列中被阻塞的執行緒
            unparkSuccessor(h);
        return true;
    }
    return false;
}
複製程式碼

1.4.2.嘗試釋放鎖

protected final boolean tryRelease(int releases) {
    // 計算待更新的state值
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 待更新的state值為0,說明持有鎖的執行緒未重入,一旦釋放鎖其他執行緒將能獲取
    if (c == 0) {
        free = true;
        // 清除鎖的持有執行緒標記
        setExclusiveOwnerThread(null);
    }
    // 更新的state值
    setState(c);
    return free;
}
複製程式碼

我們可以看見一個很重要的抽象類AbstractQueuedSynchronizer,有關AQS稍後在死磕,可以說AQS是同步元件的基礎。

相關文章