java多執行緒之等待喚醒機制(wait-notify)
wait()、notify()、notifyAll()方法
Object類裡面提供了這幾個方法: wait():讓當前執行緒處於等待(阻塞狀態),直到其他執行緒呼叫此物件的notify()或notifyAll()方法(進入就緒狀態)。 notify():喚醒在此物件監視器上等待的單個執行緒。 notifyAll():喚醒在此物件監視器上等待的所有執行緒。 每個方法都有finnal關鍵字修飾。
為什麼這些方法要定義在Object類裡,而不定義在Thread類裡呢? 因為這些方法的呼叫必須通過鎖物件呼叫,而鎖物件可以是任意物件。所以定義在Object類裡。
一個生產者—消費者問題
同一資源(共享資料):一個學生物件;設定學生物件的資料(生產者);獲取學生物件的資料(消費者)。
考慮正常情況: 生產者:如果沒有資料,則生產資料,生產完後通知消費者來消費資料;如果有,則自己等待; 消費者:如果有資料,則使用資料;如果沒有,則自己等待,同時喚醒生產者生產資料。
生產者-消費者問題之——採用等待喚醒機制解決
public class Student {
String name;
int age;
boolean flag;//預設初始化為flase 表示沒有資料(姓名年齡)
}
//SetThread.java
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) { //鎖:同一個物件:s
//已有資料了,則等待
if (s.flag) {
try {
s.wait(); //必須通過鎖物件呼叫wait()方法 注意!!執行緒等待後,就立即釋放鎖。將來醒過來的時候是從這裡醒過來的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//沒有資料,就生產資料
if (x % 2 == 0) {
s.name = "hhh";
s.age = 22;
} else {
s.name = "ttt";
s.age = 33;
}
x++;
//生產完了,有資料了,通知一下消費者。它在等著呢
s.flag = true;
s.notify();//喚醒此監視器上在等待的執行緒 注意!!喚醒並不代表你可以立馬執行,你必須還得搶cpu的執行權。
//做完活了,休息1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//GetThread.java
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {//鎖:s
if (!s.flag) {
try {
s.wait();//注意!!執行緒等待後,就立即釋放鎖。將來醒過來的時候是從這裡醒過來的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
//消費完資料後,通知生產者生產
s.flag = false;
s.notify();//喚醒此監視器上在等待的執行緒
}
}
}
}
//測試類
public class StudentDemo {
public static void main(String[] args){
//建立資源物件
Student s= new Student();
//建立兩個自定義的Runnable類的物件
SetThread mrs= new SetThread(s);//通過構造方法,把同一個資源物件傳給其他類 SetThread類定義中當然要提供一個構造方法了
GetThread mrg= new GetThread(s);
//建立執行緒
//設定資料執行緒(生產者)
Thread setthread= new Thread(mrs);
//獲取資料執行緒(消費者)
Thread getthread= new Thread(mrg);
//啟動執行緒
//兩個執行緒開始搶
setthread.start();
getthread.start();
}
}
分析: 假如setthread執行緒先搶到cpu的執行權,看SetThread類。此時還沒有資料(s.flag=false),就生產資料(s(“hhh”,22)),假如在此時getthread執行緒搶到了cpu,看GetThread類。flag為false,getthread執行緒就wait()等待,注意它等待後,就立即釋放鎖。將來醒過來的時候是從這裡醒過來的。 setthread執行了flag=true,notify()喚醒了在等待的getthread執行緒。它醒過來後如果搶到cpu的執行權就接著執行,下一步 System.out.println(s.name + “—” + s.age),即獲取資料。
使用sychronized方法優化上述程式碼
資源類:
public class Student {
private String name;
private int age;
boolean flag;//預設初值為false,表示沒有資料 標記有沒有資料
public synchronized void set(String name, int age){
//已有資料,則等待
if(flag){
try {
this.wait();//同步方法的鎖物件是:this 必須用鎖物件呼叫wait、notify這些方法 Note!! 執行緒等待後,會立即釋放鎖。將來醒過來的時候是從這裡醒過來的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//沒有資料,則生產
this.name= name;
this.age= age;
flag=true;//修改標記 標記為已有資料
this.notify();//我已生產好資料了,喚醒在等待的執行緒
}
public synchronized void get(){
//沒有資料,則等待
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有資料,則消費,即獲取資料
System.out.println(name+"--"+age);
flag=false;//修改標記
this.notify();//喚醒在等待的執行緒 Note!!注意喚醒了之後,它未必執行。得看它能不能搶到cpu
}
}
自定義的兩個Runnable類實現Runnable介面:SetThread和GetThread 重寫run方法
public class SetThread implements Runnable {
private Student s;
private int x=0;
//提供一個這樣的構造方法 初始化s
public SetThread(Student s){
this.s=s;
}
@Override
public void run() {
while(true){
if(x%2==0) {
s.set("hhh", 22);
}else{
s.set("ttt", 33);
}
x++;
}
}
}
public class GetThread implements Runnable {
private Student s;
//提供一個這樣的構造方法 初始化s
public GetThread(Student s){
this.s=s;
}
@Override
public void run() {
while(true){
s.get();
}
}
}
測試類:
public class StudentDemo {
public static void main(String[] args){
//資源物件(執行緒們都在操作的共享資料)
Student s= new Student();
//建立兩個自定義的Runnable類GetThread和SetThread類的物件
GetThread gmr= new GetThread(s);//構造方法
SetThread smr= new SetThread(s);
//建立執行緒
Thread setthread= new Thread(smr);
Thread getthread= new Thread(gmr);
setthread.start();
getthread.start();
}
}
執行結果: hhh–22 ttt–33 hhh–22 ttt–33 hhh–22 ttt–33 hhh–22 ttt–33 hhh–22 ttt–33 hhh–22 ttt–33 hhh–22 ttt–33 ……