故事引入

image.png

image.png
wait / notify 原理

image.png
- Owner 線程發(fā)現(xiàn)條件不滿足,調(diào)用 wait 方法,即可進入 WaitSet 變?yōu)?WAITING 狀態(tài)
WaitSet 里的線程是獲取過鎖 又放棄了,EntryList 中的是還沒有獲得鎖 - BLOCKED 和 WAITING 的線程都處于阻塞狀態(tài),不占用 CPU 時間
- BLOCKED 線程會在 Owner 線程釋放鎖時被喚醒
- WAITING 線程會在 Owner 線程調(diào)用 notify 或 notifyAll 時喚醒,但并不意味著會立即獲得鎖,仍然會進入 EntryList 重新競爭
wait / notify API
- obj.wiat() 讓進入 obj 監(jiān)視器的線程到 WaitSet 等待
- obj.notify() 選中 obj 監(jiān)視器的 WaitSet 中的某個線程被喚醒
- obj.notifyAll() obj 監(jiān)視器的 WaitSet 中的全部線程都被喚醒
他們都是線程之間進行協(xié)作的手段,都屬于 obj 對象的方法。必須獲得此對象的鎖,才能調(diào)用這兩個方法。(線程只有變成 obj 對象的監(jiān)視器的 Owner 才能調(diào)用)
/**
* 1、wait / notify / notifyAll
* 2、帶一個參數(shù)的 wait :等待設定時間后,如果沒有被其他線程喚醒,就不等了,繼續(xù)向下執(zhí)行;如果還沒到等待時間,另一個線程來喚醒,就被喚醒,繼續(xù)向下執(zhí)行;
* 3、帶兩個參數(shù)的 wait:wait(毫秒,納秒),不會精確到納秒,多一毫秒;
*/
public class BiasedDemo5 {
final static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock){
System.out.println("線程 t1 開始執(zhí)行");
try {
lock.wait();
// lock.wait(1000); // 即使沒有其他線程喚醒,1s 后 繼續(xù)執(zhí)行后續(xù)代碼
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程 t1 完成其他業(yè)務");
}
},"t1").start();
new Thread(()->{
synchronized (lock){
System.out.println("線程 t2 開始執(zhí)行");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程 t2 完成其他業(yè)務");
}
},"t2").start();
Thread.sleep(2000);
synchronized (lock){
// lock.notify(); // 只喚醒一個
lock.notifyAll();// 全部喚醒,全部執(zhí)行
}
}
}
wait / notify 的正確使用姿勢(step1-5)
sleep(long n) 和 wait(long n) 的區(qū)別
1)sleep 是 Thread 的靜態(tài)方法,wait 是 Object的方法
2)sleep 不需要強制和 synchronized 配合使用,但 wait 必須和 synchronized 聯(lián)合使用,獲取對象鎖
3)如果 sleep 和 synchronized 聯(lián)合使用,在 sleep 的時候不會釋放對象鎖,但 wait 在等待是會釋放對象鎖
4)sleep 和 wait 都不占用 CPU 時間,狀態(tài)都是 TIMED_WAITING
public class BiasedDemo6 {
final static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (lock){
System.out.println("線程 t1 開始執(zhí)行");
try {
// lock.wait();
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程 t1 完成其他業(yè)務");
}
},"t1").start();
Thread.sleep(1000);
synchronized (lock){
System.out.println("主線程獲得鎖,開始執(zhí)行");
}
}
}
/**
* STEP 0(使用 wait / notify 前)
**/
public class BiasedDemo7 {
final static Object room = new Object();
static boolean hasCigarette = false;
public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 線程[t1], 有沒有煙?" +hasCigarette);
if (!hasCigarette){
System.out.println(sdf.format(new Date()) + " 線程[t1], 沒有煙");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sdf.format(new Date()) + " 線程[t1], 有沒有煙?" +hasCigarette);
if(hasCigarette){
System.out.println(sdf.format(new Date()) + " 線程[t1], 有煙,開始干活");
}
}
},"t1").start();
for (int i = 0; i < 5; i++) {
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 線程[其他],開始干活");
}
},"其他").start();
}
Thread.sleep(1000);
new Thread(()->{
// synchronized (room){
hasCigarette = true;
System.out.println(sdf.format(new Date()) + " 線程[送煙的],來送煙了");
// }
},"送煙的").start();
}
}
輸出:(注意看前邊的時間哈)
05:33:58 線程[t1], 有沒有煙?false
05:33:58 線程[t1], 沒有煙
05:33:59 線程[送煙的],來送煙了
05:34:00 線程[t1], 有沒有煙?true
05:34:00 線程[t1], 有煙,開始干活
05:34:00 線程[其他],開始干活
05:34:00 線程[其他],開始干活
05:34:00 線程[其他],開始干活
05:34:00 線程[其他],開始干活
05:34:00 線程[其他],開始干活
- 其他干活的線程,都要一直阻塞,效率太低
- 線程 t1 必須睡足 2s 后才能醒來,就算煙提前送到,也無法立刻醒來
- 加了 synchronized(room) 后,就好比 t1 在里面反鎖了門睡覺,煙根本沒法送進門,main 線程沒加 synchronized 就好像 main 線程是翻窗戶進來的
- 解決方法:使用 wait - notify 機制
/**
* STEP 1(使用 wait / notify)
**/
public class BiasedDemo8 {
final static Object room = new Object();
static boolean hasCigarette = false;
public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 線程[t1], 有沒有煙?" +hasCigarette);
if (!hasCigarette){
System.out.println(sdf.format(new Date()) + " 線程[t1], 沒有煙");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sdf.format(new Date()) + " 線程[t1], 有沒有煙?" +hasCigarette);
if(hasCigarette){
System.out.println(sdf.format(new Date()) + " 線程[t1], 有煙,開始干活");
}
}
},"t1").start();
for (int i = 0; i < 5; i++) {
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 線程[其他],開始干活");
}
},"其他").start();
}
Thread.sleep(1000);
new Thread(()->{
synchronized (room){
hasCigarette = true;
System.out.println(sdf.format(new Date()) + " 線程[送煙的],來送煙了");
room.notify();
}
},"送煙的").start();
}
}
輸出
05:51:00 線程[t1], 有沒有煙?false
05:51:00 線程[t1], 沒有煙
05:51:00 線程[其他],開始干活
05:51:00 線程[其他],開始干活
05:51:00 線程[其他],開始干活
05:51:00 線程[其他],開始干活
05:51:00 線程[其他],開始干活
05:51:01 線程[送煙的],來送煙了
05:51:01 線程[t1], 有沒有煙?true
05:51:01 線程[t1], 有煙,開始干活
- 解決了其他線程被阻塞的問題
- 但如果有其他線程也在等待條件呢?
虛假喚醒:
/**
* STEP 2(錯誤喚醒/虛假喚醒)
**/
public class BiasedDemo9 {
final static Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 線程[小南], 有沒有煙?" +hasCigarette);
if (!hasCigarette){ //while (!hasCigarette){
System.out.println(sdf.format(new Date()) + " 線程[小南], 沒有煙,等一會");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sdf.format(new Date()) + " 線程[小南], 有沒有煙?" +hasCigarette);
if(hasCigarette){
System.out.println(sdf.format(new Date()) + " 線程[小南], 有煙,開始干活");
}
}
},"小南").start();
new Thread(()->{
synchronized (room){
System.out.println(sdf.format(new Date()) + " 線程[小女], 有沒有外賣?" +hasTakeout);
if (!hasTakeout){//while (!hasTakeout){
System.out.println(sdf.format(new Date()) + " 線程[小女], 沒有外賣,等一會");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sdf.format(new Date()) + " 線程[小女], 有沒有外賣?" +hasTakeout);
if(hasTakeout){
System.out.println(sdf.format(new Date()) + " 線程[小女], 有外賣,開始干活");
}
}
},"小女").start();
Thread.sleep(1000);
new Thread(()->{
synchronized (room){
hasTakeout = true;
System.out.println(sdf.format(new Date()) + " 線程[送貨的],來送外賣了");
room.notify();
//room.notifyAll();
}
},"送貨的").start();
}
}
輸出:
06:02:46 線程[小南], 有沒有煙?false
06:02:46 線程[小南], 沒有煙,等一會
06:02:46 線程[小女], 有沒有外賣?false
06:02:46 線程[小女], 沒有外賣,等一會
06:02:47 線程[送貨的],來送外賣了
06:02:47 線程[小女], 有沒有外賣?true
06:02:47 線程[小女], 有外賣,開始干活
06:02:47 線程[小南], 有沒有煙?false
//線程1
synchronized(room){
while(條件不成立){
room.wait();
}
// 干活
}
// 線程2
synchronized(room){
room.notifyAll();
}