java ReentrantLock 分析

來源:互聯網
上載者:User

標籤:java reentrantlock 機制 多線程


並發編程中經常用到的莫非是這個ReentrantLock這個類,線程擷取鎖和釋放鎖。還有一個則是synchronized,常用來多線程式控制制擷取鎖機制。


先寫一個簡單的例子。

package com.multi.thread;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class AQSDemo {public static void main(String[] args) {Lock lock = new ReentrantLock(true);MyThread t1 = new MyThread("t1", lock);MyThread t2 = new MyThread("t2", lock);MyThread t3 = new MyThread("t3", lock);t1.start();t2.start();t3.start();}}class MyThread extends Thread {private Lock lock;public MyThread(String name, Lock lock) {super(name);this.lock = lock;}@Overridepublic void run() {lock.lock();try {System.out.println(Thread.currentThread() + "  is running ");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();}}}


這是個簡單的用ReentrantLock的代碼。


知識點理解:

ReentranctLock:

1) 可重新進入性:大致意思就是如果一個函數能被安全的重複執行,那麼這個函數是可重複的。聽起來很繞口。

2)可重新進入鎖:一個線程可以重複的擷取它已經擁有的鎖。


特性:

1)ReentrantLock可以在不同的方法中使用。

2)支援公平鎖和非公平鎖概念

    static final class NonfairSync extends Sync;(非公平鎖)

    static final class FairSync extends Sync;(公平鎖)

3)支援中斷鎖,收到中斷訊號可以釋放其擁有的鎖。

4)支援逾時擷取鎖:tryLock方法是嘗試擷取鎖,支援擷取鎖的是帶上時間限制,等待一定時間就會返回。


ReentrantLock就先簡單說一下AQS(AbstractQueuedSynchronizer)。java.util.concurrent包下很多類都是基於AQS作為基礎開發的,Condition,BlockingQueue以及線程池使用的worker都是基於起實現的,其實就是將負雜的繁瑣的並發過程封裝起來,以便其他的開發工具更容易的開發。其主要通過volatile和Unsafe類的原子操作,來實現阻塞和同步。


AQS是一個抽象類別,其他類主要通過重載其tryAcquire(int arg)來擷取鎖,和tryRelease來釋放鎖。

AQS不在這裡做分析,會有單獨的一篇文章來學習AQS。


ReentrantLock類裡面主要有三個類,Sync,NonfairSync,FairSync這三個類,NonfairSync與FairSync類繼承自Sync類,Sync類繼承自AbstractQueuedSynchronizer抽象類別。


Sync是ReentrantLock實現公平鎖和非公平鎖的主要實現,預設情況下ReentrantLock是非公平鎖。

    Lock lock = new ReentrantLock(true);   :true則是公平鎖,false就是非公平鎖,什麼都不傳也是非公平鎖預設的。


非公平鎖:

    lock.lock();點進去代碼會進入到,ReentranctLock內部類Sync。

    

    abstract static class Sync extends AbstractQueuedSynchronizer {        private static final long serialVersionUID = -5179523762034025860L;        /**         * Performs {@link Lock#lock}. The main reason for subclassing         * is to allow fast path for nonfair version.         */        abstract void lock();                ......省略。      }

這個抽象類別Sync的裡有一個抽象方法,lock(),供給NonfairSync,FairSync這兩個實作類別來實現的。這個是一個模板方法設計模式,具體的邏輯供給子類來實現。

非公平鎖的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() {            if (compareAndSetState(0, 1))                setExclusiveOwnerThread(Thread.currentThread());            else                acquire(1);        }        ......省略    }

其實重點看這個compareAndSetState(0,1),這個其實一個原子操作,是cas操作來擷取線程的資源的。其代表的是如果原來的值是0就將其設為1,並且返回true。其實這段代碼就是設定private volatile int state;,這個狀態的。

其實現原理就是通過Unsafe直接得到state的記憶體位址然後直接操作記憶體的。設定成功,就說明已經擷取到了鎖,如果失敗的,則會進入:

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

這個方法裡,這個過程是先去判斷鎖的狀態是否為可用,如果鎖已被持有,則再判斷持有鎖的線程是否未當前線程,如果是則將鎖的持有遞增,這也是java層實現可重新進入性的原理。如果再次失敗,則進入等待隊列。就是要進去等待隊列了AQS有一個內部類,是Node就是用來存放擷取鎖的線程資訊。


AQS的線程阻塞隊列是一個雙向隊列,提供了FiFO的特性,Head節點表示頭部,tail表示尾部。

1)節點node,維護一個volatile狀態,維護一個prev指向向前一個隊列節點,根據前一個節點的狀態來判斷是否擷取鎖。

2)當前線程釋放的時候,只需要修改自身的狀態即可,後續節點會觀察到這個volatile狀態而改變擷取鎖。volatile是放在記憶體中的,共用的,所以前一個節點改變狀態後,後續節點會看到這個狀態資訊。


擷取鎖失敗後就會加入到隊列裡,但是有一點,不公平鎖就是,每個新來的線程來擷取所得時候,不是直接放入到隊列尾部,而是也去cas修改state狀態,看看是否擷取鎖成功。


總結非公平鎖:

首先會嘗試改變AQS的狀態,改變成功了就擷取鎖,否則失敗後再次通過判斷當前的state的狀態是否為0,如果為0,就再次嘗試擷取鎖。如果state不為0,該鎖已經被其他線程持有了,但是其它線程也可能也是自己啊,所以也要判斷一下是否是自己擷取線程,如果是則是擷取成功,且鎖的次數要加1,這是可重新進入鎖,不是則加入到node阻塞隊列裡。加入到隊列後則在for迴圈中通過判斷當前線程狀態來決定是否喲啊阻塞。可以看出在排入佇列前及阻塞前多次嘗試去擷取鎖,而避免進入線程阻塞,這是因為阻塞、喚醒都需要cpu的調度,以及環境切換,這是個重量級的操作,應盡量避免


公平鎖:

FairSync類:final void lock() {   //先去判斷鎖的狀態,而不是直接去擷取acquire(1);}AQS類:public final void acquire(int arg) {    if (!tryAcquire(arg) &&        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))        selfInterrupt();}FairSync類:protected final boolean tryAcquire(int acquires) {    final Thread current = Thread.currentThread();    int c = getState();    if (c == 0) {        //hasQueuedPredecessors判斷是否有前節點,如果有就不會嘗試去擷取鎖        if (!hasQueuedPredecessors() &&            compareAndSetState(0, acquires)) {            setExclusiveOwnerThread(current);            return true;        }    }    else if (current == getExclusiveOwnerThread()) {        int nextc = c + acquires;        if (nextc < 0)            throw new Error("Maximum lock count exceeded");        setState(nextc);        return true;    }    return false;}


公平鎖,主要區別是:什麼事都要有個先來後到,先來的有先。擷取鎖的時候是先看鎖是否可用並且是否有節點,就是是否有阻塞隊列。有的話,就是直接放入到隊列尾部,而不是擷取鎖。


釋放鎖:

public void unlock() {sync.release(1);}public final boolean release(int arg) {    if (tryRelease(arg)) {        Node h = head;        if (h != null && h.waitStatus != 0)            unparkSuccessor(h);        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;}


釋放鎖是很簡單的,就是先去改變state這個狀態的值,改變後如果狀態為0,則說明釋放成功了,如果直接可重新進入了多次,也要釋放很多次的鎖。


釋放過程:

Head節點就是當前持有鎖的線程節點,當釋放鎖時,從頭結點的next來看,頭結點的下一個節點如果不為null,且waitStatus不大於0,則跳過判斷,否則從隊尾向前找到最前的一個waitStatus的節點,然後通過LockSupport.unpark(s.thread)喚醒該節點線程。可以看出ReentrantLock的非公平鎖只是在擷取鎖的時候是非公平的,如果進入到等待隊列後,在head節點的線程unlock()時,會按照進入的順序來得到喚醒,保證了隊列的FIFO的特性。


參考文章:

http://silencedut.com/2017/01/09/%E7%94%B1ReentrantLock%E5%88%86%E6%9E%90JUC%E7%9A%84%E6%A0%B8%E5%BF%83AQS/


http://www.cnblogs.com/leesf456/p/5383609.html


https://github.com/pzxwhc/MineKnowContainer/issues/16

本文出自 “10093778” 部落格,請務必保留此出處http://10103778.blog.51cto.com/10093778/1930644

java   ReentrantLock   分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.