死磕Java——ReentrantLock

1、死磕Java——ReentrantLock

ReentrantLockjava.util.concurrent.locks包下一個可重入的默認是非公平的鎖,ReentrantLock類是Lock接口的一個使用很頻繁的實現類,類結構以下圖:java

image-20190508213733422

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

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更新頭節點源碼以下:ui

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

說明:僅當隊列中原值爲null時更新成功。this

當隊列不爲空的時候經過CAS更新尾節點源碼以下:spa

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是同步組件的基礎。指針

相關文章
相關標籤/搜索