一、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)換:

對線程的狀態(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();
}
}
}
}