淺析ReentrantLock可重入鎖

Lock鎖接口在JAVA SE5之后,出現(xiàn)在并發(fā)包中.它提供了與synchronized關(guān)鍵字一樣的同步功能.只是在使用時需要顯式地獲取和釋放鎖,缺點就是缺少像synchronized那樣隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取與釋放的可操作性,可中斷的獲取鎖以及超時獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性。

可重入鎖和不可重入鎖

  • 可重入鎖:一個線程調(diào)用一個加鎖的方法后,還可以調(diào)用其他加同一把鎖的方法.
  • 不可重入鎖:一個線程獲取到一把鎖之后,該線程是無法調(diào)用其他加了該鎖的方法.這種情況,容易造成死鎖.比如:方法一添加了不可重入鎖,方法二添加了不可重入鎖,并且調(diào)用了方法一.這種情況下,調(diào)用方法二就會出現(xiàn)死鎖的情況.那么.重入鎖就可以避免這種情況.
  • 常用的可重入鎖:
    Sychronized,java.util.concurrent.locks.ReentrantLock

ReentrantLock基本使用

ReentrantLock對資源進行加鎖,同一時刻只會有一個線程能夠占有鎖.當前鎖被線程占有時,其他線程會進入掛起狀態(tài),直到該鎖被釋放,其他掛起的線程會被喚醒并開始新的競爭.

public class ReentrantLockExample {
    private ReentrantLock reentrantLock = new ReentrantLock();

    private void test(){
        reentrantLock.lock();
        System.out.println("進行原子操作");
        try {
            Thread.sleep(3000l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        ReentrantLockExample reentrantLockExample = new ReentrantLockExample();
        for (int i = 0; i < 5; i++) {
            executorService.submit(new Thread(reentrantLockExample::test));
        }
    }
}

通過控制臺的輸出信息可知:每隔三秒輸出一次信息.

ReentrantLock很大程度的依賴了抽象類AbstractQueuedSynchronizer,這里需要先對AbstractQueuedSynchronizer進行了解.看我下面的這篇文章

ReentrantLock又有公平鎖和非公平鎖之分,所以可以看到在源碼中有兩個鎖的實現(xiàn)


公平鎖:每個線程的資源競爭是公平的.按照自身線程調(diào)用lock方法的順序來獲取鎖,即先到先得.
非公平鎖:每個線程的資源競爭是順序不定,誰的優(yōu)先級高,那么誰就會先獲得鎖.

ReentrantLock幾個重要方法的剖析

  • 構(gòu)造方法:ReentrantLock的無參構(gòu)造器,默認將局部變量sync賦值為NonfairSycn.
/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
  • lock():調(diào)用sycn內(nèi)部類的lock方法.
1.獲取一把鎖,如果該鎖沒有被其他線程持有,則立即返回,并將鎖計數(shù)器加一;
2.如果當前線程已經(jīng)持有該鎖,則將鎖計數(shù)器加一,并立即返回.
3.如果該鎖被其他線程持有,那么當前線程的目的將會無效并進入休眠狀態(tài).直到鎖計數(shù)器的值為1.
public void lock() {
        sync.lock();
    }

這里先看一下,公平鎖的lock方法的實現(xiàn).

FairSync:默認調(diào)用AbstractQueuedSynchronizer的acquire()方法.
final void lock() {
            acquire(1);
        }
AbstractQueuedSynchronizer:
public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

非公平鎖的lock方法實現(xiàn):

NonfairSync:
final void lock() {
        //通過CAS操作,將AQS中的stateOffset從0改為1.
        if (compareAndSetState(0, 1))
             //將當前線程設(shè)為獨享線程
              setExclusiveOwnerThread(Thread.currentThread());
        else
             //否則,再次請求同步狀態(tài).一般參數(shù)為0是釋放鎖,參數(shù)為1是獲取鎖
              acquire(1);
}
AbstractQueuedSynchronizer:其中tryAcquire()是由具體的實現(xiàn)類實現(xiàn)的.
public final void acquire(int arg) {
        if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
這里分為兩步進行分析:
1.NonfairSync:執(zhí)行tryAcquire方法
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        //獲取當前AQS的狀態(tài)
        int c = getState();
        if (c == 0) {//同步狀態(tài)為0時,執(zhí)行CAS操作
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }//當前線程已經(jīng)獲取到鎖,因為當前是重入鎖,則state+1,并返回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;
}
2.NonfairSync:tryAcquire方法返回false之后,會執(zhí)行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;
        //尾節(jié)點不為空
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

因為NonfairSync是非公平鎖,所以對于新來的線程和同步隊列的線程,都能調(diào)用這個方法來獲取到鎖.
由于實現(xiàn)過于復(fù)雜,這里總結(jié)來說:當多個線程賴競爭鎖時,只會有一個線程能夠獲取到鎖,那么其他線程將會通過CAS操作(保證數(shù)據(jù)的一致性)來將當前線程添加到同步隊列中,當所有線程進入到同步隊列之后,就會進入自旋狀態(tài)(死循環(huán)判斷當前線程所在節(jié)點的前驅(qū)節(jié)點是否為head節(jié)點)去嘗試獲取同步狀態(tài).

  • lockInterruptibly()或者tryLock()
    可中斷的獲取方式.兩者最終都會調(diào)用方法:
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

如果檢測到線程的中斷,將會直接拋出異常.

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 作者: 一字馬胡 轉(zhuǎn)載標志 【2017-11-03】 更新日志 前言 在java中,鎖是實現(xiàn)并發(fā)的關(guān)鍵組件,多個...
    一字馬胡閱讀 44,319評論 1 32
  • 一、 概述 本文首先介紹Lock接口、ReentrantLock的類層次結(jié)構(gòu)以及鎖功能模板類AbstractQue...
    等一夏_81f7閱讀 1,143評論 0 0
  • 第三章 Java內(nèi)存模型 3.1 Java內(nèi)存模型的基礎(chǔ) 通信在共享內(nèi)存的模型里,通過寫-讀內(nèi)存中的公共狀態(tài)進行隱...
    澤毛閱讀 4,495評論 2 21
  • 文|辛凡 談及應(yīng)酬,不論男女,應(yīng)該都是一副苦大仇深相。會有哪個人,喜歡應(yīng)酬呢? 參與自己不想?yún)⒓拥膱龊?;說著自己不...
    辛凡閱讀 876評論 2 5
  • 我現(xiàn)在只追海賊王、火影、死神這三部漫畫,一般在SF在線漫畫上看。這地方的好處就是看漫畫的時候點一下圖片就會翻下一頁...
    Pope怯懦懦地閱讀 542評論 0 0

友情鏈接更多精彩內(nèi)容