1. 程式人生 > >java多執行緒之等待喚醒機制(wait-notify)

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 ……