ReentrantLock源碼分析(三)

synchronized有個重要的功能千诬,可以通過object中的wait()和 notify()方法實現(xiàn)生產(chǎn)者/消費者智润。ReentrantLock基于Condition也同樣可以實現(xiàn),而且相對于synchronized的無差別通知仑性,ReentrantLock可以選擇性的通知,減少了很多無用的線程競爭。本文主要就是分析下ReentrantLock是怎么通過Condition實現(xiàn)生產(chǎn)者/消費者模式的馏艾。

public class ProducerCustomerWithLock {
    Executor pool = Executors.newFixedThreadPool(5);
    private List<String> storeList = new LinkedList<>();//倉庫
    //倉庫容量
    private int MAX_VALUE = 5;
    //倉庫為空
    private int MIN_VALUE = 0;
    // 線程鎖
    private Lock lock = new ReentrantLock();
    //倉庫滿了,綁定生產(chǎn)者線程
    private Condition full = lock.newCondition();

    //倉庫為空奴愉,綁定消費者線程
    private Condition empty = lock.newCondition();
    //生產(chǎn)者
    private class producer implements Runnable {

        @Override
        public void run() {
            while (true) {
                produce();
            }
        }

        private void produce() {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入倉庫琅摩,準(zhǔn)備生產(chǎn)!");

            try {
                lock.lock();
                if (storeList.size() == MAX_VALUE) {
                    System.out.println("倉庫已滿,等待消費");
                    Thread.sleep(1000);
                    full.await(); //當(dāng)前線程等待,讓其他線程繼續(xù)執(zhí)行,可以看出wait是釋放鎖的
                }
                if (storeList.size() < MAX_VALUE) {
                    String product = "產(chǎn)品" + new Random().nextInt();
                    storeList.add(product);
                    System.out.println(Thread.currentThread().getName() + "往倉庫中生產(chǎn)了一個產(chǎn)品锭硼!" + product);
                }
                Thread.sleep(1000);
                empty.signalAll();//喚醒消費者線程
            } catch (InterruptedException e) {
                System.out.println("中斷異常");
//                    e.printStackTrace();
            }
            finally {
                lock.unlock();
            }
        }
    }

    private class consumer implements Runnable {

        @Override
        public void run() {
            while (true) {
                consume();
            }
        }

        private void consume() {
            System.out.println(Thread.currentThread().getName() + "進(jìn)入倉庫房资,準(zhǔn)備消費!");

                try {
                    lock.lock();
                    if (storeList.size() == MIN_VALUE) {
                        System.out.println("倉庫已空,等待生產(chǎn)");
                        Thread.sleep(1000);
                        empty.await(); //當(dāng)前線程等待,讓其他線程繼續(xù)執(zhí)行,可以看出wait是釋放鎖的
                    }
                    if (storeList.size() > MIN_VALUE) {
                        System.out.println(Thread.currentThread().getName() + "從倉庫取得產(chǎn)品:" + storeList.remove(0));
                    }
                    Thread.sleep(1000);
                    full.signalAll();//喚醒生產(chǎn)者線程
                } catch (InterruptedException e) {
                    System.out.println("中斷異常");
//                    e.printStackTrace();
                }
                finally {
                    lock.unlock();
                }
            }

    }

    //啟動生產(chǎn)者和消費者線程
    public void start() {
        for (int i = 1; i < 5; i++) {
            pool.execute(new producer());
            pool.execute(new consumer());
        }

    }

    public static void main(String[] args) {
        ProducerCustomerWithLock pc = new ProducerCustomerWithLock();
        pc.start();
    }
}

打印結(jié)果如下檀头,生產(chǎn)者消費者交替運行:

pool-1-thread-1進(jìn)入倉庫轰异,準(zhǔn)備生產(chǎn)!
pool-1-thread-2進(jìn)入倉庫暑始,準(zhǔn)備消費搭独!
pool-1-thread-3進(jìn)入倉庫,準(zhǔn)備生產(chǎn)廊镜!
pool-1-thread-1往倉庫中生產(chǎn)了一個產(chǎn)品牙肝!產(chǎn)品-885144207
pool-1-thread-4進(jìn)入倉庫,準(zhǔn)備消費嗤朴!
pool-1-thread-5進(jìn)入倉庫配椭,準(zhǔn)備生產(chǎn)!
pool-1-thread-1進(jìn)入倉庫雹姊,準(zhǔn)備生產(chǎn)股缸!
pool-1-thread-2從倉庫取得產(chǎn)品:產(chǎn)品-885144207
pool-1-thread-2進(jìn)入倉庫,準(zhǔn)備消費吱雏!
pool-1-thread-3往倉庫中生產(chǎn)了一個產(chǎn)品乓序!產(chǎn)品-1433422526
pool-1-thread-3進(jìn)入倉庫,準(zhǔn)備生產(chǎn)坎背!
pool-1-thread-4從倉庫取得產(chǎn)品:產(chǎn)品-1433422526
pool-1-thread-4進(jìn)入倉庫替劈,準(zhǔn)備消費!
pool-1-thread-5往倉庫中生產(chǎn)了一個產(chǎn)品得滤!產(chǎn)品-137356193
pool-1-thread-5進(jìn)入倉庫陨献,準(zhǔn)備生產(chǎn)!
pool-1-thread-1往倉庫中生產(chǎn)了一個產(chǎn)品懂更!產(chǎn)品831540660
...

接下來就直接進(jìn)入正題吧眨业,看下ConditionObject.await()方法急膀。

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

先判斷線程狀態(tài)是否被中斷,如果被中斷則拋出異常龄捡。然后開始構(gòu)建Condition隊列卓嫂,看下addConditionWaiter()方法。

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

先判斷condition尾節(jié)點的waitStatus是否是condition狀態(tài)聘殖。如果不是只能是cancel狀態(tài)晨雳,則把隊列中是cancel狀態(tài)的節(jié)點移除。找到第一個是condition狀態(tài)的尾節(jié)點奸腺。

新建一個Node節(jié)點餐禁,模式是CONDITION,如果之前不存在尾節(jié)點突照。則把當(dāng)前節(jié)點作為頭結(jié)點和尾節(jié)點帮非,否則把當(dāng)前節(jié)點置為尾節(jié)點。然后執(zhí)行fullyRelease(node)方法讹蘑。

final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

首先進(jìn)入到await的方法的前提條件是獲取到鎖末盔,所以這一步是釋放鎖并且喚醒AQS隊列頭結(jié)點的后置節(jié)點。這里為什么要釋放鎖呢座慰,很簡單await方法會阻塞當(dāng)前線程陨舱。當(dāng)前線程如果不釋放鎖就會導(dǎo)致后面的線程獲取不到鎖從而阻塞。極大的影響性能還可能造成死鎖角骤。所以await方法是會釋放鎖的隅忿。

再看isOnSyncQueue(node)是判斷當(dāng)前節(jié)點是否在AQS隊列中,如果不在則阻塞當(dāng)前節(jié)點所在線程邦尊。直到signal方法喚醒該線程背桐。那么先看下signal():

public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
 private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

首先把當(dāng)前節(jié)點waitStatus狀態(tài)CAS操作為0。如果設(shè)置失敗蝉揍,且該節(jié)點為condition頭結(jié)點链峭,則把該節(jié)點排除出隊列。
如果設(shè)置成功又沾,則把該節(jié)點插入到AQS隊列中弊仪。也就是說Signal()喚醒后不是立即執(zhí)行的。而是進(jìn)入到AQS隊列中排隊杖刷。
如果該節(jié)點被取消了或者已經(jīng)被設(shè)置成了SIGNAL励饵,則取消阻塞該節(jié)點所在線程。其他情況由AQS頭節(jié)點喚醒滑燃。

再回過頭看await方法役听。

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

之前睡眠的線程被喚醒了,改節(jié)點已經(jīng)加入到了AQS隊列可以退出循環(huán)了,然后主要就是去獲取鎖并返回線程的中斷狀態(tài)典予。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亥揖,一起剝皮案震驚了整個濱河市政溃,隨后出現(xiàn)的幾起案子捣作,更是在濱河造成了極大的恐慌卜录,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捂敌,死亡現(xiàn)場離奇詭異艾扮,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)黍匾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門栏渺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呛梆,“玉大人锐涯,你說我怎么就攤上這事√钗铮” “怎么了纹腌?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長滞磺。 經(jīng)常有香客問我升薯,道長,這世上最難降的妖魔是什么击困? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任涎劈,我火速辦了婚禮,結(jié)果婚禮上阅茶,老公的妹妹穿的比我還像新娘蛛枚。我一直安慰自己,他們只是感情好脸哀,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布蹦浦。 她就那樣靜靜地躺著,像睡著了一般撞蜂。 火紅的嫁衣襯著肌膚如雪盲镶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天蝌诡,我揣著相機(jī)與錄音溉贿,去河邊找鬼。 笑死浦旱,一個胖子當(dāng)著我的面吹牛宇色,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼代兵,長吁一口氣:“原來是場噩夢啊……” “哼尼酿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起植影,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤思币,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后惶我,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绸贡,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡听怕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年尿瞭,在試婚紗的時候發(fā)現(xiàn)自己被綠了声搁。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捕发。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡爬骤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出骤铃,到底是詐尸還是另有隱情惰爬,我是刑警寧澤惫企,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站巩掺,受9級特大地震影響页畦,放射性物質(zhì)發(fā)生泄漏豫缨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望招狸。 院中可真熱鬧瓤湘,春花似錦弛说、人聲如沸翰意。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至霞幅,卻和暖如春量瓜,著一層夾襖步出監(jiān)牢的瞬間绍傲,已是汗流浹背耍共。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工试读, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留荠耽,地道東北人骇塘。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓款违,卻偏偏與公主長得像插爹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子力穗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

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