Lock介面之Condition介面

彼岸舞發表於2020-11-04

之前在寫顯示鎖的是後,在顯示鎖的介面中,提到了new Condition這個方法,這個方法會返回一個Condition物件

簡單介紹一下

Condition介面:

  任意一個Java物件,都擁有一組監視器方法(定義在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,這些方法與synchronized同步關鍵字配合,可以實現等待/通知模式。之前寫過一篇執行緒之間的協作(等待通知模式)是使用Object的wait和notify/notifyAll+Synchronized寫的

  換而言之,synchronized關鍵字想要實現等待/通知模式,需要呼叫以上的四種方法。

  然後我們的Condition介面也提供了能夠實現等待/通知模式,是與Lock配合實現的。

  我感覺這個Condition和那個差不多,也是用來完成執行緒之間的協作的

  但是二者在使用方式上以及功能特性上還是有所差別的。

Object對比Condition:

 

 由此表可以看出,condition介面可以有多個等待佇列,而object監視器方法只有一個佇列,而且還不支援在等待狀態響應中斷,還不支援當前執行緒釋放鎖並進入等待狀態到將來的某個時間。

示例:

  也不打算寫新的示例了,用這個Condition介面改造一下之前使用等待通知模式的那個案例吧

  Condition定義了等待/通知兩種型別的方法,當前執行緒呼叫這些方法時,需要提前獲取到Condition物件關聯的鎖。Condition物件是由Lock物件(呼叫Lock物件的newCondition()方法)建立出來的。其實就是,Condition是依賴Lock物件的。就像使用wait/notify需要依賴Synchronized鎖一樣,Condition的使用方式比較簡單,需要注意在呼叫方法前獲取鎖

建立等待通知類

package org.dance.day4.condition;

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

/**
 * 類說明:使用Condition介面實現等待通知模式
 */
public class ExpressCond {

    public final static String CITY = "ShangHai";

    /**
     * 快遞運輸里程數
     */
    private int km;

    /**
     * 快遞到達地點
     */
    private String site;

    /**
     * 建立顯示鎖
     */
    private final Lock lock = new ReentrantLock();

    /**
     * 檢測城市變化
     */
    private final Condition siteCond = lock.newCondition();

    /**
     * 檢測公里數變化
     */
    private final Condition kmCond = lock.newCondition();

    public ExpressCond() {
    }

    public ExpressCond(int km, String site) {
        this.km = km;
        this.site = site;
    }

    /* 變化公里數,然後通知處於wait狀態並需要處理公里數的執行緒進行業務處理*/
    public void changeKm() {
        // 獲取鎖
        lock.lock();
        try {
            this.km = 101;
            // 喚醒在kmCond 上 等待的執行緒
            kmCond.signal();
        } finally {
            lock.unlock();
        }
    }

    /* 變化地點,然後通知處於wait狀態並需要處理地點的執行緒進行業務處理*/
    public void changeSite() {
        // 獲取鎖
        lock.lock();
        try {
            this.site = "BeiJing";
            // 喚醒在siteCond 上 等待的執行緒
            siteCond.signal();
        } finally {
            lock.unlock();
        }
    }

    /*當快遞的里程數大於100時更新資料庫*/
    public void waitKm() {
        lock.lock();
        try {
            while (this.km <= 100) {
                try {
                    kmCond.await();
                } catch (InterruptedException e) {
                    // 處理執行緒中斷
                    Thread.currentThread().interrupt();
                    e.printStackTrace();
                }
                System.out.println("check km thread[" + Thread.currentThread().getId()
                        + "] is be notifed.");
            }
        } finally {
            lock.unlock();
        }
        System.out.println("the Km is " + this.km + ",I will change db");
    }

    /*當快遞到達目的地時通知使用者*/
    public void waitSite() {
        lock.lock();
        try {
            while (CITY.equals(this.site)) {
                try {
                    siteCond.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("check site thread[" + Thread.currentThread().getId()
                        + "] is be notifed.");
            }
        } finally {
            lock.unlock();
        }
        System.out.println("the site is " + this.site + ",I will call user");
    }
}

通過程式碼可以看見,我們一個鎖,是可以攜帶多個等待佇列的

建立測試類

package org.dance.day4.condition;

/**
 *類說明:測試Lock和Condition實現等待通知
 */
public class TestCond {
    private static ExpressCond express = new ExpressCond(0,ExpressCond.CITY);

    /*檢查里程數變化的執行緒,不滿足條件,執行緒一直等待*/
    private static class CheckKm extends Thread{
        @Override
        public void run() {
            express.waitKm();
        }
    }

    /*檢查地點變化的執行緒,不滿足條件,執行緒一直等待*/
    private static class CheckSite extends Thread{
        @Override
        public void run() {
            express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<3;i++){
            new CheckSite().start();
        }
        for(int i=0;i<3;i++){
            new CheckKm().start();
        }

        Thread.sleep(1000);
        express.changeKm();//快遞里程變化
    }
}

執行結果:

check km thread[14] is be notifed.
the Km is 101,I will change db

通過執行結果,我們可以清晰的看到,他是直接喚醒了,在公里數變化上等待的執行緒的,在之前的等待通知模式中,也就是wait/notify/notifyAll+Sync實現的等待通知模式中,推薦大家使用notifyAll()來喚醒正在等待中的執行緒,但是在使用Condition介面中,推薦大家使用signal,而不是signalAll().為啥呢?因為wait/notify/notifyAll是Object的方法,在指定的物件中等待的可能是多個執行緒,分別在檢測不同的變數,可能造成訊號的攔截,所以推薦使用全部喚醒,,但是在使用Condition上卻不是,因為他是多個等待佇列,他清晰的知道自己應該喚醒那個執行緒,所以推薦使用signal,至於Condition的實現分析暫時先不寫,等寫完AQS再寫方便,大家理解,我感覺不是所有相關的知識都要堆在一起,要是理解不了,再深入也沒用

作者:彼岸舞

時間:2020\11\04

內容關於:併發程式設計

本文來源於網路,只做技術分享,一概不負任何責任

相關文章