溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JAVA之sleep/wait/notify/notifyAll的作用有哪些

發(fā)布時間:2021-09-10 11:20:51 來源:億速云 閱讀:145 作者:柒染 欄目:大數(shù)據(jù)

這篇文章將為大家詳細(xì)講解有關(guān)JAVA之sleep/wait/notify/notifyAll的用法有哪些,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

簡介

首先對幾個相關(guān)的方法做個簡單解釋,Object中有幾個用于線程同步的方法:wait、notify、notifyAll。

public class Object {
    public final native void wait(long timeout) throws InterruptedException;
    public final native void notify();
    public final native void notifyAll();
}
  • wait: 釋放當(dāng)前鎖,阻塞直到被notify或notifyAll喚醒,或者超時,或者線程被中斷(InterruptedException)

  • notify: 任意選擇一個(無法控制選哪個)正在這個對象上等待的線程把它喚醒,其它線程依然在等待被喚醒

  • notifyAll: 喚醒所有線程,讓它們?nèi)ジ偁?,不過也只有一個能搶到鎖

  • sleep: 不是Object中的方法,而是Thread類的靜態(tài)方法,讓當(dāng)前線程持有鎖阻塞指定時間

sleep和wait

sleep和wait都可以讓線程阻塞,也都可以指定超時時間,甚至還都會拋出中斷異常InterruptedException。

而它們最大的區(qū)別就在于,sleep時線程依然持有鎖,別人無法進(jìn)當(dāng)前同步方法;wait時放棄了持有的鎖,其它線程有機(jī)會進(jìn)入該同步方法。多次提到同步方法,因為wait必須在synchronized同步代碼塊中,否則會拋出異常IllegalMonitorStateException,notify也是如此,可以說wait和notify是就是為了在同步代碼中做線程調(diào)度而生的。

下面一個簡單的例子展現(xiàn)sleep和wait的區(qū)別:

import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {

    // 日志行號記錄
    private AtomicInteger count = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Main main = new Main();
        // 開啟兩個線程去執(zhí)行test方法
        new Thread(main::test).start();
        new Thread(main::test).start();
    }

    private synchronized void test() {
        try {
            log("進(jìn)入了同步方法,并開始睡覺,1s");
            // sleep不會釋放鎖,因此其他線程不能進(jìn)入這個方法
            Thread.sleep(1000);
            log("睡好了,但沒事做,有事叫我,等待2s");
            //阻塞在此,并且釋放鎖,其它線程可以進(jìn)入這個方法
            //當(dāng)其它線程調(diào)用此對象的notify或者notifyAll時才有機(jī)會停止阻塞
            //就算沒有人notify,如果超時了也會停止阻塞
            wait(2000);
            log("我要走了,但我要再睡一覺,10s");
            //這里睡的時間很長,因為沒有釋放鎖,其它線程就算wait超時了也無法繼續(xù)執(zhí)行
            Thread.sleep(10000);
            log("走了");
            notify();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    // 打印日志
    private void log(String s) {
        System.out.println(count.incrementAndGet() + " "
                + new Date().toString().split(" ")[3]
                + "\t" + Thread.currentThread().getName() + " " + s);
    }
}

/* 輸出:

1 00:13:23	Thread-0 進(jìn)入了同步方法,并開始睡覺,1s
2 00:13:24	Thread-0 睡好了,但沒事做,有事叫我,等待2s
3 00:13:24	Thread-1 進(jìn)入了同步方法,并開始睡覺,1s
4 00:13:25	Thread-1 睡好了,但沒事做,有事叫我,等待2s
5 00:13:26	Thread-0 我要走了,但我要再睡一覺,10s
6 00:13:36	Thread-0 走了
7 00:13:36	Thread-1 我要走了,但我要再睡一覺,10s
8 00:13:46	Thread-1 走了

*/

對輸出做個簡單解釋(已經(jīng)看懂代碼的童鞋可以跳過):

1 00:13:23	Thread-0 進(jìn)入了同步方法,并開始睡覺,1s     // Thread-0首先進(jìn)入同步方法,Thread-1只能門外候著
2 00:13:24	Thread-0 睡好了,但沒事做,有事叫我,等待2s  // Thread-0 sleep 1秒這段時間,Thread-1沒進(jìn)來,證明sleep沒有釋放鎖
3 00:13:24	Thread-1 進(jìn)入了同步方法,并開始睡覺,1s     // Thread-0開始wait后Thread-1馬上就進(jìn)來了,證明wait釋放了鎖
4 00:13:25	Thread-1 睡好了,但沒事做,有事叫我,等待2s  // Thread-1也打算wait 2秒(2秒后真的能醒來嗎?)
5 00:13:26	Thread-0 我要走了,但我要再睡一覺,10s      // Thread-0已經(jīng)wait超時醒來了,這次準(zhǔn)備sleep 10s
6 00:13:36	Thread-0 走了                           // 10s過去了Thread-0都sleep結(jié)束了,那個說要wait 2s的Thread-1還沒動靜,證明超時也沒用,還得搶到鎖
7 00:13:36	Thread-1 我要走了,但我要再睡一覺,10s     // Thread-0退出同步代碼后,Thread-1才終于得到了鎖,能行動了
8 00:13:46	Thread-1 走了

notify和notifyAll

同樣是喚醒等待的線程,同樣最多只有一個線程能獲得鎖,同樣不能控制哪個線程獲得鎖。

區(qū)別在于:

  • notify:喚醒一個線程,其他線程依然處于wait的等待喚醒狀態(tài),如果被喚醒的線程結(jié)束時沒調(diào)用notify,其他線程就永遠(yuǎn)沒人去喚醒,只能等待超時,或者被中斷

  • notifyAll:所有線程退出wait的狀態(tài),開始競爭鎖,但只有一個線程能搶到,這個線程執(zhí)行完后,其他線程又會有一個幸運(yùn)兒脫穎而出得到鎖

如果覺得解釋的不夠明白,代碼來一波:

import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {

    private AtomicInteger count = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Main main = new Main();
        // 開啟兩個線程去執(zhí)行test方法
        for (int i = 0; i < 10; i++) {
            new Thread(main::testWait).start();
        }
        Thread.sleep(1000);
        for (int i = 0; i < 5; i++) {
            main.testNotify();
        }
    }

    private synchronized void testWait() {
        try {
            log("進(jìn)入了同步方法,開始wait");
            wait();
            log("wait結(jié)束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private synchronized void testNotify() {
        notify();
    }

    private void log(String s) {
        System.out.println(count.incrementAndGet() + " "
                + new Date().toString().split(" ")[3]
                + "\t" + Thread.currentThread().getName() + " " + s);
    }

}

/* 輸出:

1 00:59:32	Thread-0 進(jìn)入了同步方法,開始wait
2 00:59:32	Thread-9 進(jìn)入了同步方法,開始wait
3 00:59:32	Thread-8 進(jìn)入了同步方法,開始wait
4 00:59:32	Thread-7 進(jìn)入了同步方法,開始wait
5 00:59:32	Thread-6 進(jìn)入了同步方法,開始wait
6 00:59:32	Thread-5 進(jìn)入了同步方法,開始wait
7 00:59:32	Thread-4 進(jìn)入了同步方法,開始wait
8 00:59:32	Thread-3 進(jìn)入了同步方法,開始wait
9 00:59:32	Thread-2 進(jìn)入了同步方法,開始wait
10 00:59:32	Thread-1 進(jìn)入了同步方法,開始wait
11 00:59:33	Thread-0 wait結(jié)束
12 00:59:33	Thread-6 wait結(jié)束
13 00:59:33	Thread-7 wait結(jié)束
14 00:59:33	Thread-8 wait結(jié)束
15 00:59:33	Thread-9 wait結(jié)束

*/

例子中有10個線程在wait,但notify了5次,然后其它線程一直阻塞,這也就說明使用notify時如果不能準(zhǔn)確控制和wait的線程數(shù)對應(yīng),可能會導(dǎo)致某些線程永遠(yuǎn)阻塞。

使用notifyAll喚醒所有等待的線程:

import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

public class Main {

    private AtomicInteger count = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        Main main = new Main();
        // 開啟兩個線程去執(zhí)行test方法
        for (int i = 0; i < 5; i++) {
            new Thread(main::testWait).start();
        }
        Thread.sleep(1000);
        main.testNotifyAll();
    }

    private synchronized void testWait() {
        try {
            log("進(jìn)入了同步方法,開始wait");
            wait();
            log("wait結(jié)束");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private synchronized void testNotifyAll() {
        notifyAll();
    }

    private void log(String s) {
        System.out.println(count.incrementAndGet() + " "
                + new Date().toString().split(" ")[3]
                + "\t" + Thread.currentThread().getName() + " " + s);
    }

}

/* 輸出:

1 01:03:24	Thread-0 進(jìn)入了同步方法,開始wait
2 01:03:24	Thread-4 進(jìn)入了同步方法,開始wait
3 01:03:24	Thread-3 進(jìn)入了同步方法,開始wait
4 01:03:24	Thread-2 進(jìn)入了同步方法,開始wait
5 01:03:24	Thread-1 進(jìn)入了同步方法,開始wait
6 01:03:25	Thread-1 wait結(jié)束
7 01:03:25	Thread-2 wait結(jié)束
8 01:03:25	Thread-3 wait結(jié)束
9 01:03:25	Thread-4 wait結(jié)束
10 01:03:25	Thread-0 wait結(jié)束

*/

只需要調(diào)用一次notifyAll,所有的等待線程都被喚醒,并且去競爭鎖,然后依次(無序)獲取鎖完成了后續(xù)任務(wù)。

為什么wait要放到循環(huán)中使用

一些源碼中出現(xiàn)wait時,往往都是伴隨著一個循環(huán)語句出現(xiàn)的,比如:

private synchronized void f() throws InterruptedException {
    while (!isOk()) {
        wait();
    }
    System.out.println("I'm ok");
}

既然wait會被阻塞直到被喚醒,那么用if+wait不就可以了嗎?其他線程發(fā)現(xiàn)條件達(dá)到時notify一下不就行了?

理想情況確實如此,但實際開發(fā)中我們往往不能保證這個線程被notify時條件已經(jīng)滿足了,因為很可能有某個無關(guān)(和這個條件的邏輯無關(guān))的線程因為需要線程調(diào)度而調(diào)用了notify或者notifyAll。此時如果樣例中位置等待的線程不巧被喚醒,它就會繼續(xù)往下執(zhí)行,但因為用的if,這次被喚醒就不會再判斷條件是否滿足,最終程序按照我們不期望的方式執(zhí)行下去。

關(guān)于JAVA之sleep/wait/notify/notifyAll的用法有哪些就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI