天天看點

【多線程】線程通信排程、等待集 wait() 、notify()對象等待集來幹預線程的排程

(目錄)

對象等待集來幹預線程的排程

wait 和 notify 為了處理線程排程随機性的問題。

還是那句話,多線程的排程,因為它的随機性,就導緻代碼誰先執行,誰後執行,存在太多的變數。

而我們程式員是不喜歡随機性,我們喜歡确定的東西。

需要能夠讓線程彼此之間,有一個固定的順序。

舉個例子:打籃球

籃球裡面有一個典型的操作:傳球,上籃。

那麼我們肯定得先傳球,再上籃。需要兩個隊員的互相配合。

兩個隊員也就是兩個線程。

如果是先做個上籃動作,但是球沒到,也就上了個寂寞。

一般最穩的方法,都是先傳球再上籃。

像這樣的順序,在我們實際開發中也是非常需要的。

是以我們就需要有手段去控制!

之前講到的 join 也是一種控制順序的方式,但是

join更傾向于控制線程結束

。是以

join 是有使用的局限性。

就不像 wait 和 notify 用起來更加合适。

wait 方法

其實wait()方法就是使線程停止運作。

  1. 方法wait()的作用是使目前執行代碼的線程進行等待

    ,wait( )方法是Object類的方法,該方法是用來将目前線程置入“預執行隊列”中,并且在wait()所在的代碼處停止執行,直到接到通知或被中斷為止。
  2. wait( )方法隻能在同步方法中或同步塊中調用。如果調用wait()時,沒有持有适當的鎖,會抛出異常

  3. wait( )方法執行後,目前線程釋放鎖,并一直處于等待通知狀态, 直到其他線程喚醒該線程後與其它線程競争重新擷取鎖。
【多線程】線程通信排程、等待集 wait() 、notify()對象等待集來幹預線程的排程
【多線程】線程通信排程、等待集 wait() 、notify()對象等待集來幹預線程的排程

調用 wait 方法的線程,就會陷入阻塞,阻塞到有其它線程通過 notify 來通知。

public class ThreadTest2 {
    public static void main(String[] args) {
        Object locker = new Object();
        Thread t = new Thread() {
            @Override
            public void run() {
                synchronized (locker) {
                    System.out.println("t 開始運作");
                    try {
                        locker.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(" t 運作結束");
                }
            }
        };
        t.start();
    }
}
           
【多線程】線程通信排程、等待集 wait() 、notify()對象等待集來幹預線程的排程

notify 方法

一個線程執行到object.wait()之後就一直等待下去,那麼程式肯定不能一直這麼等待下去了。這個時候就需要使用到了另外一個方法喚醒的方法notify()。

notify方法就是使停止的線程繼續運作

  1. 方法notify()也要在同步方法或同步塊中調用

    ,該方法是用來通知那些可能等待該對象的對象鎖的其它線程,對其發出通知notify,并使它們重新擷取該對象的對象鎖。

    如果有多個線程等待,則有線程規劃器随機挑選出一個呈wait狀态的線程。

  2. 在notify()方法後,目前線程不會馬上釋放該對象鎖,要等到執行notify()方法的線程将程式執行完,也就是退出同步代碼塊之後才會釋放對象鎖。
public class Test18 {
    // 為了兩個線程能夠順利互動,我們建立一個鎖對象
    private static Object locker = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            //進行 wait
            synchronized (locker){
                System.out.println("wait 之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait 之後");
            }
        });
        t1.start();

        // 為了大家能更清楚的觀察到現象,我這裡使用 sleep 延遲3s
        Thread.sleep(3000);

        Thread t2 = new Thread(()->{
            // 進行 notify
            synchronized (locker){
                System.out.println("notify 之前");
                locker.notify();
                System.out.println("notify 之後");
            }
        });
        t2.start();
    }
}
           
【多線程】線程通信排程、等待集 wait() 、notify()對象等待集來幹預線程的排程

根據這種機制我們可以編排多個線程的順序

【多線程】線程通信排程、等待集 wait() 、notify()對象等待集來幹預線程的排程

notifyAll 方法

前面說的 wait 和 notify 都是針對同一個對象來操作。

例如:

現在有

一個對象 o

,被 10個線程調用了

o.wait

此時

10 個 線程都是阻塞狀态

如果調用了

o.notify

,就會把

10個線程中的一個給喚醒

【随機喚醒:不确定下一個被喚醒的線程是哪一個】

被喚醒的線程就會繼續往下執行。其他線程仍處于阻塞狀态。

如果

調用的 o.notifyAll

,就會

把所有的線程全部喚醒

wait 在被喚醒之後,會重新嘗試擷取到鎖,這個過程就會發生競争

喚醒所有的線程,都去搶鎖。

搶到的,才可以繼續往下執行。

沒搶到的線程,繼續等待,等待下一次的 notifyAll。**

wait方法總結

  • wait( )

    的作用是

    讓目前線程進入等待狀态

    ,同時,wait( )也會讓目前線程釋放它所持有的鎖。

直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,使目前線程被喚醒(進入“就緒狀态”)

  • notify()和notifyAll()的作用,則是喚醒目前對象上的等待線程

notify()是喚醒單個線程

notifyAll()是喚醒所有的線程

, 然後這些線程再去競争那同一把鎖。(不建議使用notifyAll )
  • wait(long timeout)讓目前線程處于“等待(阻塞)狀态

  1. “直到其他線程調用此對象的notify()方法或 notifyAll() 方法
  2. 或者超過指定的時間量”,目前線程被喚醒(進入“就緒狀态”)

總結:

​ wait() 的工作過程就是:
  1. 釋放對象鎖(是以必須有一個鎖才能正常執行)

  2. 等待通知

    (時間可能會長, 因為不參與後序鎖的競争直到有其他線程調用該對象的notify()去喚醒該線程)
  3. 收到通知後 嘗試重新擷取對象鎖 繼續往下執行

特别注意

  1. wait() 和 notify() 操作必須放在 synchronized 的代碼塊之内使用

    否則會報異常
  2. 調用wait() 和 notify() 的對象必須是同一個對象 才能起到等待和喚醒操作

    更準确的說: 因為wait 和 notify 要在 synchronized 的代碼塊内 是以上鎖的對象也應該和 wait() 和 notify() 的對象必須是同一個對象 這樣就是4個對象得保持一緻

notify方法總結

  • 一個線程執行到object.wait()之後就一直等待下去,那麼程式肯定不能一直這麼等待下去了。這個時候就需要使用到了另外一個方法喚醒的方法notify()。
  • notify方法就是使停止的線程繼續運作。
  1. 方法notify()也要在同步方法或同步塊中調用,該方法是用來通知那些可能等待該對象的對象鎖的其它線程,對其發出通知notify,并使它們重新擷取該對象的對象鎖。如果有多個線程等待,則有線程規劃器随機挑選出一個呈wait狀态的線程。
  2. 在notify()方法後,目前線程不會馬上釋放該對象鎖,要等到執行notify()方法的線程将程式執行完,也就是退出同步代碼塊之後才會釋放對象鎖。

注意:

喚醒線程不能過早,如果在還沒有線程在等待中時,過早的喚醒線程,這個時候就會出現先喚醒,在等待的效果了。

public class ThreadTest3 {
    public static void main(String[] args) {
        Object locker = new Object();
        Thread t = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker) {
                    System.out.println(" t 開始運作");
                    try {
                        locker.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(" t 運作結束");
                }
            }
        };
        t.start();

        Thread t1 = new Thread() {
            @Override
            public void run() {
                synchronized (locker) {
                    System.out.println(" t1 開始運作");
                    locker.notify();
                    System.out.println(" t1 運作結束");
                }
            }
        };
        t1.start();

        Thread t2 = new Thread() {
            @Override
            public void run() {
                synchronized (locker) {
                    System.out.println(" t2 開始運作");
                    System.out.println(" t2 運作結束");
                }
            }
        };
        t2.start();
    }
}


           

競态條件問題

  • 競态條件問題

    當一個 t 線程執行了wait()方法釋放了對象鎖之後 另外一個 t1 線程擷取了鎖然後立即執行了notify()方法區通知 t 線程 但是 t 線程還沒有執行到等待通知的那段代碼 是以這個通知就錯過了 這樣線程 t 就會一直等待下去 這就是一個競态條件問題
  • 為了解決和

    避免這個問題 事實上當我們在執行wait() 操作的時候 釋放鎖和等待接收通知是處于一個原子性質上執行的 最後才執行嘗試重新擷取鎖

wait與sleep的對比

  1. wait用于線程之間的通信 sleep用于讓線程阻塞一段時間
  2. wait之前必須要有鎖 wait之後會釋放鎖 等被喚醒之後再重嘗試請求鎖
  3. sleep是無視鎖的存在的 即不會要求有鎖 也不會去釋放鎖
  4. wait是object的方法
  5. sleep是thread的靜态方法
  6. 唯一相同點就是都可以讓線程放棄執行一段時間