【詳解】JUC之Condition

引出

在使用Lock之前,我們使用的最多的同步方式應該是synchronized關鍵字來實現同步方式了。配合Object的wait()、notify()系列方法可以實現等待/通知模式。

Condition接口也提供了類似Object的監視器方法,與Lock配合可以實現等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。

初步使用

利用Condition實現一個簡單的生產者和消費者

public class ConditionTest {
    private final static Lock lock = new ReentrantLock(true);

    private final  static Condition condition = lock.newCondition();

    private static int data = 0;

    private static volatile  boolean noUse = true;

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


        new Thread(()->{
            while (true){
                buildData();
            }
        }).start();
        new Thread(()->{
            while (true){
                useData();
            }
        }).start();
    }

    /**
     * 生產數據
     */
    private static void buildData(){
        try {
            lock.lock();    //synchronized key word  #moitor enter
            while (noUse){
                condition.await();  // monitor.wait()
            }
            data++;
            System.out.println("P:" + data);
            Thread.sleep(1000);
            noUse = true;
            condition.signal();  // monitor.notify()
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // synchronized  end  #moitor end
        }
    }


    /**
     * 消費數據
     */
    private static void useData(){
        try {
            lock.lock();    //synchronized key word  #moitor enter
            while (!noUse){
                condition.await();  // monitor.wait()
            }
            System.out.println("C:" + data);
            Thread.sleep(1000);
            noUse = false;
            condition.signal();  // monitor.notify()
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  // synchronized  end  #moitor end
        }
    }


}

結果

C:0
P:1
C:1
P:2
C:2
......

根據上面的代碼有幾個問題:

  • 1.不使用Condition效果相同,是否可以不使用Condition?
  • 2.生產者獲得鎖,但是陷入await時,鎖還沒有釋放,生產者怎麼獲得到鎖?
  • 3.如果不使用lock只用Condition會怎麼樣?

問題解答

第一個問題

不使用Condition,依然會看起來一樣的原因是:使用了公平鎖,會儘可能保證兩個線程的交替

第二個問題

在接口的上面有上述解釋

The lock associated with this is atomically released and the current thread


In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.

可以發現:await和Object.wait()類似,都會自動的釋放鎖,並且在喚起後需要重新獲得鎖

第三個問題

問題的提出是基於第二個問題,既然鎖的獲取沒有意義,是否可以去掉?

如果不加lock,會拋出一個異常,說明:想要await必須需要獲得到鎖

Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
	at condition.ConditionTest.useData(ConditionTest.java:66)
	at condition.ConditionTest.lambda$main$1(ConditionTest.java:27)
	at java.lang.Thread.run(Thread.java:748)

wait和await的區別

等待隊列

public class ConditionTest {
    private final static Lock lock = new ReentrantLock();

    private final static Condition condition = lock.newCondition();

    private static int data = 0;

    private static volatile boolean noUse = true;

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

        for (int i = 0; i < 10; i++) {
            creatThread();
        }

        Thread.sleep(3000);
        
            lock.lock();
            condition.signalAll();
            lock.unlock();
            Thread.sleep(1000);
       


    }

    public static void creatThread(){
        new Thread(() -> {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + " await.");
                condition.await();
                System.out.println(Thread.currentThread().getName() + " no await.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


}

結果

Thread-0 await.
Thread-1 await.
Thread-2 await.
Thread-3 await.
Thread-4 await.
Thread-5 await.
Thread-6 await.
Thread-7 await.
Thread-8 await.
Thread-9 await.
Thread-0 no await.
Thread-1 no await.
Thread-2 no await.
Thread-3 no await.
Thread-4 no await.
Thread-5 no await.
Thread-6 no await.
Thread-7 no await.
Thread-8 no await.
Thread-9 no await.

可以發現await的等待隊列是個先進先出的隊列

總結

這是摘自《Java併發編程的藝術》
在這裏插入圖片描述

最大差異

wait await
是配合synchronized關鍵字的 是配合Lock鎖的
等待隊列的喚醒受到JVM的影響,是隨機的喚醒 等待隊列FIFO的,先進入先喚醒
不可以被打斷 可以被打斷
等待隊列只有一個 每一個Condition都具有一個等待隊列,可以創建多個Condition

其他地方在使用上並無差異

利用Condition實現生產者和消費者

主要是創建兩個Condition對象第一個對象是消費者隊列,第二個是生產者隊列。這樣可以實現兩個隊列的分離。

public  class ConditionTest {
    private  final static Lock lock = new ReentrantLock(false);

    private final static Condition PRODUCE_CONDITION = lock.newCondition();

    private final static Condition CONSUMER_CONDITION = lock.newCondition();

    private final static LinkedList<Long> TIMESTAMP_POOL = new LinkedList<>();

    private final static int MAX_CAPACITY = 100;

    private static int data = 0;



    public static void main(String[] args) throws InterruptedException {
        IntStream.range(0,6).boxed().forEach(ConditionTest::beginProduce);
        IntStream.range(0,13).boxed().forEach(ConditionTest::beginConsume);


    }

    private static void beginProduce(int i){
        new Thread(()->{
            while (true){
                produce();
                sleep(2);
            }
        },"P-" + i).start();
    }

    private static void beginConsume(int i){
        new Thread(()->{
            while (true){
                consum();
                sleep(2);
            }
        },"C-" + i).start();
    }

   private static void produce(){
        try {
            lock.lock();
            while (TIMESTAMP_POOL.size()>=MAX_CAPACITY){
                PRODUCE_CONDITION.await();
            }
            long value =  System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + "P->" + value);
            TIMESTAMP_POOL.addLast(value);
            CONSUMER_CONDITION.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


    private static void consum(){
        try {
            lock.lock();
            while (TIMESTAMP_POOL.isEmpty()){
                CONSUMER_CONDITION.await();
            }
            long value =  TIMESTAMP_POOL.removeFirst();
            System.out.println(Thread.currentThread().getName() + "C->" + value);
            PRODUCE_CONDITION.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private static void sleep(long sec){

        try {
            TimeUnit.SECONDS.sleep(sec);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


}

與 ReentrantLock的關係

protected Collection<Thread> getWaitingThreads(Condition condition)
  • 獲取此條件下,阻塞的線程集合
public int getWaitQueueLength(Condition condition)
  • 獲取此條件下,阻塞的線程數目
public boolean hasWaiters(Condition condition)
  • 獲取此條件是否有線程在阻塞

總結

  • 可以發現Condition比較靈活,可以配合ReentrantLock,對阻塞的隊列進行調試
  • Condition可以創建多個,可以實現不同角色等待隊列的分離
  • Condition在阻塞時是可以被打斷的,方便對線程進行關閉
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章