JUC中Lock和ReentrantLock介紹及源碼解析

Lock框架是jdk1.5新增的钮莲,作用和synchronized的作用一樣,所以學習的時候可以和synchronized做對比倒淫。在這里先和synchronized做一下簡單對比座菠,然后分析下Lock接口以及ReentrantLock的源碼和說明姜钳。具體的其他的Lock實現(xiàn)的分析在后面會慢慢介紹。

Lock框架和synchronized

有關(guān)synchronized的作用和用法不在具體說明领迈,應(yīng)該都很熟悉了彻磁。而Lock有著和synchronized一樣的語意,但是比synchronized多了一些功能狸捅,單單就從Lock接口定義來看衷蜓,比synchronized多出來的功能有:

  • 可中斷的獲取鎖,就是獲取鎖的線程可以響應(yīng)中斷尘喝。
  • 可以嘗試獲取鎖磁浇,也就是非阻塞獲取鎖,一個線程可以嘗試去獲取鎖朽褪,獲取成功就持有鎖并返回true置吓,否則返回false。
  • 帶超時的嘗試獲取鎖缔赠,就是在嘗試獲取鎖的時候交洗,會有超時時間,當?shù)竭_了指定的時間后橡淑,還未獲取到鎖构拳,就返回false。

除了定義之外梁棠,Lock框架還和synchronized有不一樣的是:

  • Lock需要顯示的加鎖和釋放鎖置森,而且一定要在finally中去釋放鎖。而synchronized則不需要我們?nèi)リP(guān)心鎖的釋放符糊。
  • 鎖的公平性凫海,Lock接口并沒有定義有關(guān)公平性的方法,而是在具體的實現(xiàn)類中使用AQS來實現(xiàn)鎖的公平性男娄。

Lock接口源碼

public interface Lock {

    //獲取鎖行贪,獲取到鎖后返回
    //注意一定要記得釋放鎖
    void lock();

    //可中斷的獲取鎖
    //獲取鎖的時候漾稀,如果線程正在等待獲取鎖,則該線程能響應(yīng)中斷
    void lockInterruptibly() throws InterruptedException;

    //嘗試獲取鎖建瘫,當線程獲取鎖的時候崭捍,獲取成功與否都會立即返回
    //不會一直等著去獲取鎖
    boolean tryLock();

    //帶有超時時間的嘗試獲取鎖
    //在一定的時間內(nèi)獲取到鎖會返回true
    //在這段時間內(nèi)被中斷了,會返回
    //在這段時間內(nèi)啰脚,沒有獲取到鎖殷蛇,會返回false
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    //釋放鎖
    void unlock();

    //獲取一個Condition對象。
    Condition newCondition();
}

Lock接口的定義并不復(fù)雜橄浓,獲取鎖釋放鎖以及非阻塞式的獲取鎖等方法的定義粒梦。其實想象一下日常使用的時候,也大概是如此荸实,獲取鎖匀们,釋放鎖,獲取鎖的時候沒有得到鎖准给,我就轉(zhuǎn)一圈回來再試試昼蛀,等到一定時間之后,我就不要了圆存,走了叼旋。接口的定義沒有太多要說的,接下來看Lock接口的實現(xiàn)沦辙。

Lock接口的實現(xiàn)

Lock接口主要的實現(xiàn)是ReentrantLock重入鎖夫植,另外還有ConcurrentHashMap中的Segment繼承了ReentrantLock,在ReentrantReadWriteLock中的WriteLock和ReadLock也實現(xiàn)了Lock接口油讯。

ReentrantLock

ReentrantLock是一個可重入的互斥鎖详民,等同于synchronized,但是比synchronized更強大靈活陌兑,減少死鎖發(fā)生的概率沈跨。我們上面說Lock框架提供了公平鎖的機制,就是在ReentrantLock中有提供公平鎖機制的實現(xiàn)兔综,默認為非公平鎖饿凛。

在繼續(xù)看ReentrantLock的各個方法實現(xiàn)之前,首先需要了解下內(nèi)部是怎么實現(xiàn)公平鎖和非公平鎖的软驰,其實想一下也簡單涧窒,比如我就是一可重入鎖ReentrantLock,你想從我這獲得到公平的還是不公平的锭亏,但是不能我說什么就是什么纠吴,我這里有一個天平(AQS),這個是大家公認的可以實現(xiàn)公平和不公平的機器慧瘤,你找我要戴已,我就給天平說一聲固该,他來操作,然后我再把結(jié)果給你糖儡。(越描述越復(fù)雜7セ怠)。幾乎ReentrantLock中所有的操作都會交給Sync去實現(xiàn)休玩。

有關(guān)AQS這里不做介紹著淆,在AQS專門的文章有介紹劫狠,請自行查閱把拴疤。接下來就看看我拿著的兩把鎖,公平和不公平独泞。

Sync

Sync是公平和非公平兩種的基類呐矾,直接看代碼,看不明白的話懦砂,可以先看下面公平和非公平的解析蜒犯,然后再返回來:

//繼承自AQS
abstract static class Sync extends AbstractQueuedSynchronizer {

    //由具體的子類實現(xiàn),即公平和非公平的有不同實現(xiàn)
    abstract void lock();

    //非公平的嘗試獲取
    final boolean nonfairTryAcquire(int acquires) {
        //當前線程
        final Thread current = Thread.currentThread();
        //當前AQS同步器的狀態(tài)
        int c = getState();
        //狀態(tài)為0荞膘,說明沒有人獲取鎖
        if (c == 0) {
            //嘗試獲取罚随,獲取成功設(shè)為獨占模式
            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;
    }
    
    //嘗試釋放
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    
    //是否是獨占
    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }
    
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Methods relayed from outer class
    //獲取持有者線程
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    //獲取重入數(shù)
    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }
    //是否鎖了
    final boolean isLocked() {
        return getState() != 0;
    }

    //
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}

FairSync

公平鎖的實現(xiàn)羽资,有關(guān)公平的實現(xiàn)淘菩,是此類進行處理的。

//繼承自Sync
static final class FairSync extends Sync {
    //獲取鎖
    //公平的lock方法交給了AQS的acquire方法去處理
    //acquire方法采用獨占模式屠升,并且忽略中斷
    //而AQS獲取鎖的實現(xiàn)是先使用tryAcquire方法獲取潮改,獲取不到就加入到隊列中,一直嘗試獲取腹暖,直到成功返回汇在,
    //tryAcquire的實現(xiàn)又是具體的子類實現(xiàn)的,下面的tryAcquire方法就是公平的tryAcquire實現(xiàn)
    //
    final void lock() {
        //參數(shù)1是AQS的同步狀態(tài)
        //首先了解下AQS中同步狀態(tài)的定義脏答,state
        //0表示未被獲取到鎖糕殉,1表示已經(jīng)被獲取到鎖了,大于1表示重入數(shù)
        //我們要獲取鎖殖告,肯定是想要現(xiàn)在的同步狀態(tài)為0糙麦,然后我們把狀態(tài)變成1,這樣鎖就是我們的了
        acquire(1);
    }

    //公平的tryAcquire方法實現(xiàn)
    protected final boolean tryAcquire(int acquires) {
        //當前線程
        final Thread current = Thread.currentThread();
        //獲取AQS的同步狀態(tài)
        int c = getState();
        //狀態(tài)為0的話丛肮,說明沒有其他人獲取到鎖
        if (c == 0) {
            //hasQueuedPredecessors查詢是否還有其他線程比當前線程等待獲取鎖的時間更長
            //compareAndSetState使用cas來設(shè)置狀態(tài)赡磅,預(yù)期為0,我們想要設(shè)置的值為1
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                //如果我們是等待獲取時間最長的(這就是公平宝与,我等的時間長焚廊,就該我第一個被服務(wù))
                //并且cas設(shè)置成功了冶匹,表示我們獲取到鎖了
                //設(shè)置當前線程為獨占訪問
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //往下是state不為0的情況,也就是1咆瘟,或者大于1
        //如果當前線程和獨占線程是同一個
        else if (current == getExclusiveOwnerThread()) {
            //當前狀態(tài)加上我們要獲取的參數(shù)1
            //現(xiàn)在表示的是重入數(shù)
            int nextc = c + acquires;
            //state是一個32位整型嚼隘,小于0,表示重入數(shù)超過了最大數(shù)
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            //設(shè)置當前狀態(tài)
            setState(nextc);
            return true;
        }
        return false;
    }
}

可以看到公平的tryAcquire在獲取鎖的開始只調(diào)用一次袒餐,獲取到就獲取到了飞蛹,或者已經(jīng)獲取到增加重入數(shù),沒有獲取到就返回false灸眼,如果返回false的話卧檐,AQS就會將其加入到隊列中一直嘗試獲取。

NonfairSync

//也是繼承自Sync
static final class NonfairSync extends Sync {
    //非公平的獲取鎖
    final void lock() {
        //先嘗試直接獲取鎖
        //如果能獲取到鎖焰宣,就設(shè)置為獨占模式
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //直接獲取不到的話霉囚,就會跟公平鎖一樣的流程去獲取
            //tryAcquire在下面
            acquire(1);
    }
    //這里是非公平的tryAcquire,直接調(diào)用Sync中的nofairTryAcquire方法
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

公平和非公平的區(qū)別

上面看完了代碼匕积,有點模糊盈罐,感覺代碼都差不多,到底公平和非公平差別在哪里闪唆。首先看下公平的鎖獲取盅粪,公平鎖獲取會直接調(diào)用acquire方法,acquire方法并不是直接去獲取鎖悄蕾,而是調(diào)用公平的tryAcquire方法票顾,公平的tryAcquire方法首先獲取到當前同步器狀態(tài),如果沒有人用同步器笼吟,也就是狀態(tài)為0库物,會先去判斷有沒有人比我等的時間更長,有的話我就不能獲取鎖贷帮,而是讓別人先去戚揭;如果我是等待最長的那個,我就去使用CAS更改狀態(tài)撵枢,獲取鎖民晒。

而非公平的實現(xiàn)是,我上來就直接使用CAS獲取鎖锄禽,不問別人是不是等著很長時間了潜必,我獲取到了就是我的了,我獲取不到沃但,再調(diào)用acquire方法磁滚,然后acquire方法中調(diào)用非公平的tryAcquire方法,非公平的tryAcquire方法也是很直接,如果當前鎖沒有人用垂攘,也就是state為0维雇,我不管有沒有人比我等的時間長,我就去獲取晒他,然后設(shè)置獨占吱型。

公平鎖,獲取鎖首先去嘗試陨仅,沒有的話就排隊津滞,輪到我之后,還要去問一下有沒有等的時間更長的灼伤。非公平鎖則是不排隊触徐,直接上,沒有獲取到饺蔑,我也嘗試獲取锌介,嘗試獲取的時候我還是直接上嗜诀,不管其他人猾警。

了解完公平和非公平鎖,再去看其他方法就沒那么難了隆敢。

ReentrantLock構(gòu)造函數(shù)

//可以看到发皿,默認是非公平的
public ReentrantLock() {
    sync = new NonfairSync();
}

還可以指定公平性

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

lock方法

//直接調(diào)用公平或者非公平的同步器的lock方法
public void lock() {
    sync.lock();
}

lock方法有三種情況:

  • 如果鎖沒有被其他線程持有,當前線程立即獲得鎖并返回拂蝎,同步器狀態(tài)設(shè)為1穴墅。
  • 如果當前線程已經(jīng)持有鎖,則狀態(tài)加1温自,并立即返回玄货。
  • 如果鎖被其他線程持有,當前線程掛起直到獲取到鎖悼泌,然后返回松捉,同步器設(shè)為1。

lockInterruptibly方法

//可中斷的獲取鎖
public void lockInterruptibly() throws InterruptedException {
    //調(diào)用AQS的方法
    sync.acquireInterruptibly(1);
}

獲取鎖馆里,可以被Thread.interrupt打斷隘世。也有三種情況:

  • 如果鎖沒有被其他線程持有,當前線程立即獲得鎖并返回鸠踪,同步器狀態(tài)設(shè)為1丙者。

  • 如果當前線程已經(jīng)持有鎖,則狀態(tài)加1营密,并立即返回械媒。

  • 如果鎖被其他線程持有,當前線程會掛起去獲取鎖评汰,在這個過程會有兩種情況:

    • 當前線程獲取到了鎖纷捞,返回侣集,同步器狀態(tài)設(shè)置1。
    • 當前線程被中斷了兰绣,會拋出InterruptedException異常世分,并清除中斷狀態(tài)。

tryLock方法

//嘗試獲取鎖缀辩,不會阻塞臭埋,成功與否都會直接返回
public boolean tryLock() {
    //使用的是非公平鎖的獲取
    return sync.nonfairTryAcquire(1);
}

使用非公平的鎖獲取,如果使用了公平的臀玄,獲取的時候還要判斷別人是不是了好久瓢阴,而非公平的nonfairTryAcquire,能獲取就直接獲取到健无,獲取不到就返回false荣恐,比較直接。

如果不想破壞公平性累贤,可以使用帶有超時時間的tryLock方法叠穆。

帶超時的tryLock方法

//在超時間內(nèi),并且沒有被打斷臼膏,鎖沒有被其他線程持有硼被,就立即獲取到鎖并返回
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

如果使用的是公平鎖,如果有其他等的時間更長的線程渗磅,即便現(xiàn)在鎖沒有人持有嚷硫,當前線程也不會獲取到鎖,給等的時間更長的去獲取始鱼。

unlock方法

//釋放鎖
//直接調(diào)用AQS來釋放鎖
public void unlock() {
    sync.release(1);
}

newCondition方法

//返回一個新的Condition實例
public Condition newCondition() {
    return sync.newCondition();
}

返回的Condition實例的方法仔掸,其實和Object的wait,notify医清,notifyAll方法的作用一樣起暮。

其他方法

//重入次數(shù)
 public int getHoldCount() {
    return sync.getHoldCount();
}

//鎖是否被當前線程持有
public boolean isHeldByCurrentThread() {
    return sync.isHeldExclusively();
}

//查詢鎖是否被任何線程持有
public boolean isLocked() {
    return sync.isLocked();
}

//是否是公平鎖
public final boolean isFair() {
    return sync instanceof FairSync;
}

//返回當前擁有鎖的線程
protected Thread getOwner() {
    return sync.getOwner();
}

//查詢是否有線程在排隊獲取鎖
public final boolean hasQueuedThreads() {
    return sync.hasQueuedThreads();
}

//查詢給定的線程是否在等待獲取鎖
public final boolean hasQueuedThread(Thread thread) {
    return sync.isQueued(thread);
}

//得到正在等待獲取鎖的隊列的長度
public final int getQueueLength() {
    return sync.getQueueLength();
}

//獲取正在等待獲取鎖的所有線程
protected Collection<Thread> getQueuedThreads() {
    return sync.getQueuedThreads();
}

//查詢是否有線程正在等待給定的Condition
public boolean hasWaiters(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}

//得到正在等待一個Condition的隊列的長度
public int getWaitQueueLength(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}

//獲取所有的等待某個Condition的線程集合
protected Collection<Thread> getWaitingThreads(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
    return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}

ReentrantLock和synchronized的區(qū)別

ReentrantLock和synchronized很類似,但是比synchronized多了更多功能状勤,比如可中斷鎖鞋怀,鎖可以帶超時時間,可以嘗試非阻塞獲取鎖等持搜。ReentrantLock還提供了條件Condition密似,跟Object的wait/notify類似,但是在多個條件變量和高度競爭鎖的地方葫盼,ReentrantLock更加合適残腌。

另外AQS是重點,一定要多學幾遍,學會了抛猫,才能掌握鎖(我還不太明白s№铩)。

有關(guān)其他實現(xiàn)闺金,比如ReentrantReadWriteLock的ReadLock和WriteLock以及ConcurrentHashMap中的Segment會另行介紹逾滥。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市败匹,隨后出現(xiàn)的幾起案子寨昙,更是在濱河造成了極大的恐慌槐瑞,老刑警劉巖吼砂,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異铺峭,居然都是意外死亡槽棍,警方通過查閱死者的電腦和手機捉蚤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炼七,“玉大人缆巧,你說我怎么就攤上這事√厥” “怎么了盅蝗?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵鳖链,是天一觀的道長姆蘸。 經(jīng)常有香客問我,道長芙委,這世上最難降的妖魔是什么逞敷? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮灌侣,結(jié)果婚禮上推捐,老公的妹妹穿的比我還像新娘。我一直安慰自己侧啼,他們只是感情好牛柒,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痊乾,像睡著了一般皮壁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上哪审,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天蛾魄,我揣著相機與錄音,去河邊找鬼。 笑死滴须,一個胖子當著我的面吹牛舌狗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扔水,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼痛侍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了魔市?” 一聲冷哼從身側(cè)響起恋日,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嘹狞,沒想到半個月后岂膳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡磅网,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年谈截,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涧偷。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡簸喂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出燎潮,到底是詐尸還是另有隱情喻鳄,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布确封,位于F島的核電站除呵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏爪喘。R本人自食惡果不足惜颜曾,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秉剑。 院中可真熱鬧泛豪,春花似錦、人聲如沸侦鹏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽略水。三九已至价卤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聚请,已是汗流浹背荠雕。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工稳其, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炸卑。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓既鞠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盖文。 傳聞我的和親對象是個殘疾皇子嘱蛋,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 前言 上一篇文章《基于CAS操作的Java非阻塞同步機制》 分析了非同步阻塞機制的實現(xiàn)原理,本篇將分析一種以非同步...
    Mars_M閱讀 4,791評論 5 9
  • 作者: 一字馬胡 轉(zhuǎn)載標志 【2017-11-03】 更新日志 前言 在java中五续,鎖是實現(xiàn)并發(fā)的關(guān)鍵組件洒敏,多個...
    一字馬胡閱讀 44,144評論 1 32
  • 線程互斥同步除了使用最基本的 synchronized 關(guān)鍵字外(關(guān)于 synchronized 關(guān)鍵字的實現(xiàn)原理...
    JohnnyShieh閱讀 2,338評論 0 4
  • Java8張圖 11、字符串不變性 12疙驾、equals()方法凶伙、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,693評論 0 11
  • 我拉著你的頭發(fā)它碎,讓你不能回頭看我 你不看我函荣,我才能心硬 你不看我,我才敢肆意妄為 你不看我扳肛,我才能恨你傻挂,咬你,詛咒...
    丁一文閱讀 120評論 0 1