JAVA多線程(八) Condition源碼分析

Condition接口

Condition是一個接口,其提供的就兩個核心方法,await和signal方法。分別對應着Object的wait和notify方法。調用Object對象的監視器方法的這兩個方法,
需要在同步代碼塊裏面,即必須先獲取到鎖才能執行這兩個方法。同理,Condition調用這兩個方法,也必須先獲取到鎖,與Lock配合可以實現等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。

Object的監視器方法與Condition接口的對比

Condition簡單用法

Condition定義了等待/通知兩種類型的方法,當前線程調用這些方法時需要提前獲取到Condition對象關聯的鎖。Condition對象是由Lock對象(調用Lock對象的newCondition()方法)創建出來的,換句話說Condition是依賴Lock對象的。Condition的使用方式比較簡單,需要注意在調用方法前獲取鎖.如下面代碼所示,一般都會將Condition對象作爲成員變量。當調用await()方法後,當前線程會釋放鎖並在此等待,而其他線程調用Condition對象的signal()方法,通知當前線程後,當前線程才從await()方法返回,並且在返回前已經獲取了鎖。

package com.brian.mutilthread.condition;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class ConditionDemo {
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(ConditionDemo::run);
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (Exception e) {  }
        lock.lock();
        // 喚醒
        condition.signal();
        lock.unlock();
        log.info("    === {}     ===: {} 33333",Thread.currentThread().getName());
    }

    private static void run() {
        lock.lock();
        try {
            log.info("=== {} ===: {} 11111", Thread.currentThread().getName());
            // 等待
            condition.await();
            log.info("=== {} ===: {} 22222", Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

代碼地址:https://gitee.com/showkawa_admin/netty-annotation/blob/master/src/main/java/com/brian/mutilthread/condition/ConditionDemo.java

Condition常用API

手寫基於condition的隊列

代碼地址:https://gitee.com/showkawa_admin/netty-annotation/blob/master/src/main/java/com/brian/mutilthread/condition/BrianQueue.java

package com.brian.mutilthread.condition;


import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class BrianQueue<T> {
    private Object[] items;
    // 添加的下標,刪除的下標和數組當前數量
    private int addIndex, removeIndex, count;
    private Lock lock = new ReentrantLock();
    private Condition emptyCondition = lock.newCondition();
    private Condition fullCondition = lock.newCondition();

    public BrianQueue(int size) {
        items = new Object[size];
    }

    // 添加一個元素,如果數組滿,則添加線程進入等待狀態,直到有"空位"
    public void add(T t) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) {
                log.info("===== queue is full and be blocked ======");
                fullCondition.await();
            }
            items[addIndex] = t;
            if (++addIndex == items.length) {
                addIndex = 0;
            }
            log.info("=== add() ===: {}", addIndex);
            ++count;
            emptyCondition.signal();
        } finally {
            lock.unlock();
        }
    }

    // 由頭部刪除一個元素,如果數組空,則刪除線程進入等待狀態,直到有新添加元素
    @SuppressWarnings("unchecked")
    public T remove() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                log.info("===== queue is empty and be blocked ======");
                emptyCondition.await();
            }
            Object x = items[removeIndex];
            if (++removeIndex == items.length) {
                removeIndex = 0;
            }
            log.info("=== remove() ===: {}", removeIndex);
            --count;
            fullCondition.signal();
            return (T) x;
        } finally {
            lock.unlock();
        }
    }
}

測試類

package com.brian.mutilthread.condition;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class BrianQueueDemo {

    public static void main(String[] args) throws InterruptedException {
        BrianQueue<Integer> brianQueue = new BrianQueue<>(5);

        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(()->{
            Integer num = 0;
            while (true){
                try {
                    brianQueue.add(++num);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        executorService.execute(()->{
            while (true){
                try {
                    brianQueue.remove();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

    }
}

 

Condition await() 和 signal()源碼解讀

此處以Condition的實現類ConditionObject,ConditionObject是同步器AbstractQueuedSynchronizer的內部類來分析

public final void await() throws InterruptedException {

    if (Thread.interrupted())
        throw new InterruptedException();
    // 將當前節點添加到最後一個節點
    Node node = addConditionWaiter();
    //釋放鎖的狀態
    long 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);
}

調用Condition的await()方法(或者以await開頭的方法),會使當前線程進入等待隊列並釋放鎖,同時線程狀態變爲等待狀態。當從await()方法返回時,當前線程一定獲取了Condition相關聯的鎖。如果從隊列(同步隊列和等待隊列)的角度看await()方法,當調用await()方法時,相當於同步隊列的首節點(獲取了鎖的節點)移動到Condition的等待隊列中。

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
//獲取單向鏈表,
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

 調用Condition的signal()方法,將會喚醒在等待隊列中等待時間最長的節點(首節點),在喚醒節點之前,會將節點移到同步隊列中。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章