JUC學習之Condition和順序訪問

一、簡介

JUC提供了Lock可以方便的進行鎖操作,但是有時候我們也需要對線程進行條件性的阻塞和喚醒,這時我們就需要Condition條件變量,可以方便的對持有鎖的線程進行阻塞和喚醒。

Condition將對象監視器方法(wait、notify和notifyAll)分解到不同的對象中,通過將它們與任意鎖實現結合使用,實現每個對象具有多個等待集的效果。鎖代替同步方法和語句的使用,Condition代替對象監視器方法的使用。

Condition(也稱爲條件隊列或條件變量)提供了一種方法,讓一個線程暫停執行(“等待”),直到另一個線程通知某個狀態條件現在可能爲真。因爲對共享狀態信息的訪問發生在不同的線程中,所以必須保護它,所以某種形式的鎖與條件相關聯。等待條件提供的關鍵屬性是它自動釋放關聯的鎖並掛起當前線程,就像Object.wait一樣。

Condition實例本質上綁定到鎖。要獲取特定鎖實例的條件實例,請使用其newCondition()方法。

  • 例如,假設我們有一個有界的緩衝區,它支持put和take方法。如果嘗試在空緩衝區上執行take操作,則線程將阻塞,直到某個項可用爲止;如果在一個完整的緩衝區上嘗試put,那麼線程將阻塞,直到空間可用爲止。我們希望將put線程和take線程放在不同的等待集中,這樣我們就可以優化在緩衝區中的項或空間可用時只通知單個線程。這可以通過使用兩個Condition條件實例來實現。
class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   //非滿的條件
   final Condition notFull  = lock.newCondition(); 
   //非空的條件
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length)
       //如果滿了,則阻塞等待
         notFull.await();
       items[putptr] = x;
       if (++putptr == items.length) putptr = 0;
       ++count;
       //通知take
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0)
       //如果當前爲空, 則等待
         notEmpty.await();
       Object x = items[takeptr];
       if (++takeptr == items.length) takeptr = 0;
       --count;
       //通知需要進行put
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   }
 }

二、常用API

void

await()

導致當前線程等待,直到發出信號或中斷它。

boolean

await(long time, TimeUnit unit)

導致當前線程等待,直到發出信號或中斷它,或指定的等待時間過期。

long

awaitNanos(long nanosTimeout)

導致當前線程等待,直到發出信號或中斷它,或指定的等待時間過期。

void

awaitUninterruptibly()

導致當前線程等待,直到它被通知。

boolean

awaitUntil(Date deadline)

導致當前線程等待,直到發出信號或中斷它,或指定的截止日期過期。

void

signal()

喚醒一個正在等待的線程。

void

signalAll()

喚醒所有等待的線程。

更加詳細的介紹,可以去官網https://docs.oracle.com/javase/8/docs/api/index.html瞭解。

三、案例

下面通過一個精確通知順序訪問的案例加深對Condition的使用。

示例:多線程之間按順序調用,實現AA->BB->CC依次打印。

案例分析:

要實現按順序打印,那麼是否要有一些標誌,標誌當前需要打印AA還是BB還是CC:

/**
 * 標誌位
 * A :1
 * B : 2
 * C : 3
 */
private int number = 1;

當打印完AA後,將number修改爲2,打印完BB後,將n的umber修改爲3,打印完CC後將number修改爲1,依次重複,然後再創建三個分別代表打印AA/BB/CC的Condition綁定到Lock上。

代碼實現:

/**
 * 多線程之間按順序調用,實現AA->BB->CC依次打印
 * 也即精確通知順序訪問
 */
public class T07_ConditionDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                shareData.printAA();
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                shareData.printBB();
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                shareData.printCC();
                System.out.println("===================");
            }
        }).start();
    }
}

class ShareData {
    /**
     * 標誌位
     * A :1
     * B : 2
     * C : 3
     */
    private int number = 1;
    /**
     * 可重入鎖
     */
    private Lock lock = new ReentrantLock();
    /**
     * 打印AA的條件
     */
    private Condition conditionA = lock.newCondition();
    /**
     * 打印BB的條件
     */
    private Condition conditionB = lock.newCondition();
    /**
     * 打印CC的條件
     */
    private Condition conditionC = lock.newCondition();

    public void printAA() {
        lock.lock();
        try {
            while (number != 1) {
                conditionA.await();
            }
            for (int i = 0; i <= 4; i++) {
                System.out.println("AA");
            }
            number = 2;
            //喚醒打印BB的線程
            conditionB.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printBB() {
        lock.lock();
        try {
            while (number != 2) {
                conditionB.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("BB");
            }
            number = 3;
            //喚醒打印CC的線程
            conditionC.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printCC() {
        lock.lock();
        try {
            while (number != 3) {
                conditionC.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println("CC");
            }
            number = 1;
            //喚醒打印AA的線程
            conditionA.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

運行結果:

AA
AA
AA
AA
AA
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
AA
AA
AA
AA
AA
===================
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
===================
AA
AA
AA
AA
AA
BB
BB
BB
BB
BB
CC
CC
CC
CC
CC
===================

以上就是使用Condition實現了精確通知某個線程然後按順序訪問。

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