sleep()、wait()、notify()、notifyAll、線程五種狀態(tài)之間的關(guān)系

一、sleep()介紹篇

鋪墊:想要更好的了解sleep,要具備線程相關(guān)的知識

about1:線程的五大狀態(tài):

線程狀態(tài) 狀態(tài)解釋
新建狀態(tài)(New) 新創(chuàng)建了一個線程對象
就緒狀態(tài)(Runnable) 調(diào)用thread.start()方法后進入就緒狀態(tài)。該狀態(tài)的線程位于“可運行線程池”中,萬事俱備,只欠CPU使用權(quán)。
運行狀態(tài)(Running) 就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼
死亡狀態(tài)(Dead) 線程執(zhí)行完了或者因遇到error或exception退出了run()方法,該線程結(jié)束生命周期。
阻塞狀態(tài)(Blocked) 阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán),暫時停止運行。直到線程進入就緒狀態(tài),才有機會轉(zhuǎn)到運行狀態(tài)。

about2:線程的五大狀態(tài)之間的轉(zhuǎn)換:

image-20220311110245680.png

對線程的狀態(tài)有了了解之后再來看sleep():

1、sleep()屬于Thread類的方法。

2、可以讓線程休眠定長時間,進入到線程阻塞狀態(tài)。比如sleep(3000),執(zhí)行后3s內(nèi)線程處于阻塞狀態(tài)。

3、sleep(3000)不代表著線程休眠3s后就會返回到運行狀態(tài),事實上是返回到就緒狀態(tài)。就緒狀態(tài)獲取CPU使用權(quán)后才會進入到運行狀態(tài)。

4、有鎖時,sleep()不會釋放鎖。

針對第4點寫個實例,解釋下sleep()怎么又和鎖扯上關(guān)系了呢。

package designmode.productconsumermode.waitandsleep;

import java.util.ArrayList;

public class SleepService {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        Thread thread1 = new Thread(new Thread1(list));
        Thread thread2 = new Thread(new Thread2(list));
        thread1.start();
        thread2.start();
    }
}

class Thread1 implements Runnable{
    private ArrayList list;

    public Thread1(ArrayList list) {
        this.list = list;
    }

    @Override
    public void run() {
        synchronized (list) {
            try {
                System.out.println("線程:[" +Thread.currentThread().getName() + "]開啟");
                System.out.println(Thread.currentThread().getName() + "sleep開始時間:" + System.currentTimeMillis()/1000);
                Thread.sleep(3*1000);
                System.out.println(Thread.currentThread().getName() +"sleep休眠結(jié)束時間" + System.currentTimeMillis()/1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 嘗試在線程sleep的時候,啟動另一個線程去訪問list對象
 * 如果在線程sleep(3000)的3s內(nèi),控制臺打印了該方法的輸出語句,
 * 則證明sleep方法在休眠期間釋放了對this對象的鎖
 */
class Thread2 implements Runnable{
    private ArrayList list;

    public Thread2(ArrayList list) {
        this.list = list;
    }

    @Override
    public void run() {
        System.out.println("線程:[" +Thread.currentThread().getName() + "]開啟");
        synchronized (list) {
            System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis()/1000);
        }
    }
}






執(zhí)行結(jié)果:
線程:[Thread-0]開啟
Thread-0sleep開始時間:1646982984
線程:[Thread-1]開啟
Thread-0sleep休眠結(jié)束時間1646982987
Thread-1:1646982987

分析執(zhí)行結(jié)果:

Thread-0調(diào)用sleep(3000)方法休眠后,等了3s,Thread-1才進入到synchronized修飾的代碼塊中。

即:sleep后,只有等到休眠結(jié)束才會釋放鎖。

二、wait()介紹篇

知識鋪墊:notify/notifyAll要了解一下:

  • obj.notify:隨機喚醒對象obj的某個wait線程,使被喚醒的線程進入就緒狀態(tài)。

  • obj.notifyAll:喚醒對象obj的所有wait線程,使被喚醒的線程進入就緒狀態(tài)。

1、wait()是Object類的方法

2、wait()后,當(dāng)前線程無限休眠,進入阻塞狀態(tài)。直到被notify/notifyAll喚醒。(對應(yīng)線程五大狀態(tài)轉(zhuǎn)換圖中的:等待通知-收到通知示例)

3、wait(10*1000)后,當(dāng)前線程休眠10s,進入阻塞狀態(tài)。如果10s內(nèi)被notify/notifyAll喚醒,則休眠結(jié)束,線程進入就緒狀態(tài);如果10s內(nèi)沒有被喚醒,則10s后自動結(jié)束休眠,進入就緒狀態(tài)。

4、wait()會釋放對象鎖。以便于被別的線程拿到鎖后,通過notify/notifyAll喚醒。

5、因為wait需釋放鎖,所以必須在synchronized中使用(沒有鎖怎么釋放?沒有鎖時使用會拋出IllegalMonitorStateException(正在等待的對象沒有鎖))

針對第4點,同樣實例走一走:

package designmode.productconsumermode.waitandsleep;

public class WaitService {
    public static void main(String[] args) throws InterruptedException {
        WaitService waitService = new WaitService();
        Thread thread1 = new Thread(new Thread11(waitService));
        Thread thread2 = new Thread(new Thread22(waitService));
        thread1.start();
        Thread.sleep(1000);
        thread2.start();
    }

    public void mwait() {
        System.out.println("線程:[" +Thread.currentThread().getName() + "]開啟");
        synchronized (this) {
            try {
                System.out.println(Thread.currentThread().getName() + "開始wait:" + System.currentTimeMillis()/1000);
                // wait(2000)表示將鎖釋放2000毫秒,
                // 到時間后如果鎖沒有被其他線程占用,則再次得到鎖,然后wait方法結(jié)束,執(zhí)行后面的代碼。
                // 如果鎖被其他線程占用,則等待其他線程釋放鎖。
                // 注意,設(shè)置了超時時間的wait方法一旦過了超時時間,并不需要其他線程執(zhí)行notify也能自動解除阻塞;
                // 但是如果沒設(shè)置超時時間的wait方法必須等待其他線程執(zhí)行notify。
                this.wait(10*1000);
                System.out.println(Thread.currentThread().getName() + ":wait結(jié)束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void doit() {
        System.out.println("線程:[" +Thread.currentThread().getName() + "]開啟");
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + "執(zhí)行notify");
            this.notify();
            System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis()/1000);
        }

    }
}

class Thread11 implements Runnable{
    private WaitService waitService;

    public Thread11(WaitService waitService) {
        this.waitService = waitService;
    }

    @Override
    public void run() {
        waitService.mwait();
    }
}

class Thread22 implements Runnable{
    private WaitService waitService;

    public Thread22(WaitService waitService) {
        this.waitService = waitService;
    }

    @Override
    public void run() {
        waitService.doit();
    }
}
執(zhí)行結(jié)果:
Thread-0開始wait:1646986113
線程:[Thread-1]開啟
Thread-1執(zhí)行notify
Thread-1:1646986114
Thread-0:wait結(jié)束

分析執(zhí)行結(jié)果:

Thread-0開始wait后-->Thread-1執(zhí)行notify-->Thread-0wait結(jié)束。

可以看出來,Thread-0中,執(zhí)行完this.wait方法后,一定釋放了this對象的鎖。因為Thread-1獲取了該對象的鎖,才能通過this.notify()方法喚醒Thread-0,使其繼續(xù)往下執(zhí)行代碼。

三、sleep()和wait()比較篇

相同點

都可以使當(dāng)前線程休眠,讓出CPU的使用權(quán),進入阻塞狀態(tài)

區(qū)別

1、sleep是屬于Thread類中的方法;wait是屬于Object類中的方法。

2、sleep方法有沒有鎖都可以調(diào)用;wait方法必須在有鎖的情況下才能調(diào)用,否則會報錯。即代碼要在synchronized中。

3、sleep方法執(zhí)行后,當(dāng)前線程不會釋放當(dāng)前占據(jù)的對象鎖;wait方法執(zhí)行后會釋放該對象的鎖。

4、sleep是靜態(tài)方法;wait是實例方法。

四、notify和notifyAll相關(guān)知識擴展

notes:

notify后不會立刻喚醒線程,而是等notify所在的synchronized(obj){}代碼塊執(zhí)行完了,才會喚醒wait線程。

兩個概念:

1、鎖池:ThreadA中已經(jīng)占據(jù)了obj對象的鎖,此時ThreadB、ThreadC、ThreadD也需要obj的鎖,那么ThreadB、ThreadC、ThreadD會進入obj對象的鎖池中。

2、等待池:ThreadA中執(zhí)行了obj.wait(),ThreadA會釋放obj的鎖,而后進入obj的等待池中。我的理解就是等待被喚醒(notify/notifyAll)的池子。

例如:ThreadA、ThreadB、ThreadC、ThreadD都執(zhí)行了obj.wait,則此時obj的等待池中有(ThreadA、ThreadB、ThreadC、ThreadD),而obj.notify的作用就是隨機喚醒obj等待池中的某個線程。

這兩個概念理解起來也不難。

但是有個說法:notify/notifyAll喚醒線程也可以解釋為是將線程由等待池移動到鎖池。

這個我就不太理解了,線程A通過wait釋放了鎖-->被線程B的notify喚醒-->怎么A又要去競爭鎖了呢?

我得猜想大概是這樣的:

synchronized(obj){}給obj加鎖,進入代碼塊-->obj.wait()釋放鎖-->被notify喚醒-->需要obj鎖才能重新進入到wait所在的代碼塊中,繼續(xù)執(zhí)行wait下面的代碼。

只有等synchronized(obj){}代碼塊中所有代碼都執(zhí)行完了,這個線程才會真正的釋放掉鎖,不再需要鎖。

跑個實例試一試:這個實例可以證明notify是隨即喚醒的

package designmode.productconsumermode.waitandsleep;

import java.util.concurrent.TimeUnit;

public class WaitAndNotify {
    public static void main(String[] args) {
        Object co = new Object();
        System.out.println(co);

        for (int i = 0; i < 5; i++) {
            MyThread t = new MyThread("Thread" + i, co);
            t.start();
        }

        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("-----Main Thread notify-----");
            for (int i = 0; i < 5; i++) {
                synchronized (co) {
                    co.notifyAll();
                }
            }
            // 喚醒線程后,繼續(xù)搶占鎖,如果wait的線程不再需要鎖,那么wait后代碼可以執(zhí)行。
            // 否則的話就證明,還是需要wait過后還是需要拿到鎖才能回到wait代碼塊中
            synchronized (co) {
                while (true) {}
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class MyThread extends Thread {
        private String name;
        private Object co;

        public MyThread(String name, Object o) {
            this.name = name;
            this.co = o;
        }

        @Override
        public void run() {
            System.out.println(name + " is waiting.");
            try {
                synchronized (co) {
                    co.wait();
                }
                System.out.println(name + " has been notified.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容