線程之間的協做(等待通知模式)

等待和通知java

  等待和通知的標準範式面試

    等待方:express

      1:獲取對象的鎖編程

      2:在循環中判斷條件是否知足,不知足調用wait方法繼續阻塞,爲啥要要循環中判斷呢?由於該線程被喚醒以後可能條件依舊不知足網絡

      3:條件知足,執行業務邏輯併發

    通知方:ide

      1:獲取對象的鎖測試

      2:改變相關條件ui

      3:通知全部等待在對象的線程this

 

都是屬於Object的方法

等待:wait

通知:notify/notifyAll

需求:一個快遞在變動;里程數和地點的時候通知等待的線程處理變動後的請求

測試使用notifyAll喚醒

實體類

package org.dance.day1.wn;

/**
 * 快遞實體類
 *
 * @author ZYGisComputer
 */
public class Express {

    public final static String CITY = "ShangHai";

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

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

    public Express() {
    }

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

    /**
     * 變化千米數:而後通知處於wait狀態並須要處理千米數的線程進行業務處理
     */
    public synchronized void checkKm() {
//      變化千米數
        this.km = 101;
//      所有通知
        notifyAll();
    }

    /**
     * 變化地點:而後通知處於wait狀態並須要處理地點的線程進行業務處理
     */
    public synchronized void checkSite() {
//        變化城市
        this.site = "BeiJin";
//         所有通知
        notifyAll();
    }

    public synchronized void waitKm() {
        // 循環等待
        while (this.km <= 100) {
            try {
                wait();
                System.out.println("check km " + Thread.currentThread().getId());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("the km is " + this.km + ", I will change DB.");
    }

    public synchronized void waitSite() {
        // 循環等待
        while (CITY.equals(this.site)) {
            try {
                wait();
                System.out.println("check site " + Thread.currentThread().getId());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("the site is " + this.site + ", I will change DB.");
    }
}

測試類

package org.dance.day1.wn;

import org.dance.tools.SleepTools;

/**
 * 測試wait notify/notifyAll
 * @author ZYGisComputer
 */
public class TestWN {

    private static Express express = new Express(0,Express.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) {
        for (int i = 0; i < 3; i++) {
            new CheckKm().start();
        }
        for (int i = 0; i < 3; i++) {
            new CheckSite().start();
        }
        SleepTools.second(1);
        // 修改里程數
        express.checkKm();
    }
}

 測試結果:

check site 16
check site 15
check site 14
check km 13
the km is 101, I will change DB.
check km 12
the km is 101, I will change DB.
check km 11
the km is 101, I will change DB.

測試發現所有的線程所有被喚醒了,而後其中三個等待城市變化的線程再次進入阻塞,另外三個等待里程數變化的執行成功退出阻塞

測試使用notify喚醒

 

 返回結果:

check km 11
the km is 101, I will change DB.
check site 11

由於notify通知任意一個在這個對象上阻塞的線程,若是正好通知到了,等待里程數的,那麼也只有一個被喚醒,其餘兩個繼續阻塞,若是通知到了一個等待城市變化的那麼這個線程將繼續進入阻塞,因此看來,咱們應該儘可能使用notifyAll少用notify,由於可能發生信號丟失的狀況

/**
     * Wakes up a single thread that is waiting on this object's
     * monitor. If any threads are waiting on this object, one of them
     * is chosen to be awakened. The choice is arbitrary and occurs at
     * the discretion of the implementation. A thread waits on an object's
     * monitor by calling one of the {@code wait} methods.
     * <p>
     * The awakened thread will not be able to proceed until the current
     * thread relinquishes the lock on this object. The awakened thread will
     * compete in the usual manner with any other threads that might be
     * actively competing to synchronize on this object; for example, the
     * awakened thread enjoys no reliable privilege or disadvantage in being
     * the next thread to lock this object.
     * <p>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. A thread becomes the owner of the
     * object's monitor in one of three ways:
     * <ul>
     * <li>By executing a synchronized instance method of that object.
     * <li>By executing the body of a {@code synchronized} statement
     *     that synchronizes on the object.
     * <li>For objects of type {@code Class,} by executing a
     *     synchronized static method of that class.
     * </ul>
     * <p>
     * Only one thread at a time can own an object's monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    public final native void notify();

在源碼中能夠看到,這個方法是一個 native的

在他的描述中有一段

The choice is arbitrary and occurs at,the discretion of the implementation.

翻譯爲中文

選擇是任意的,發生在,執行的自由裁量權。

因此說notify的喚醒是隨意的,而且信號只發出一次

可是據有人說,在線程進入等待的時候會進入一個等待隊列,notify會喚醒第一個等待的線程

通過在百度上一頓搜索,瀏覽了大量的文章以後

 

 我獲得的結果就是在HotSpot虛擬機當中 notify喚醒的是阻塞線程隊列當中的第一個ObjectWaiter節點,其餘虛擬機不必定.

我以爲這個問題也已當作一個在面試的時候,你問面試官的一個技術性問題

 

做者:彼岸舞

時間:2020\09\16

內容關於:併發編程

本文來源於網絡,只作技術分享,一律不負任何責任

相關文章
相關標籤/搜索