wait()和notify()、notifyAll()

陳俊成發表於2016-09-28

今天想到了這個問題(wait()方法、notify()、notifyAll()這三個方法是不是執行了就釋放鎖呢?答案是:都會釋放鎖

  1. 為什麼 wait(), notify()和 和 notifyAll()必須在同步方法或者同步塊中 必須在同步方法或者同步塊中被呼叫?
    答:當一個執行緒需要呼叫物件的 wait()方法的時候,這個執行緒必須擁有該物件的鎖,接著它就會釋放這個物件鎖並進入等待狀態直到其他執行緒呼叫這個物件上的 notify()方法。同樣的,當一個執行緒需要呼叫物件的 notify()方法時,它會釋放這個物件的鎖,以便其他在等待的執行緒就可以得到這個物件鎖。由於所有的這些方法都需要執行緒持有物件的鎖,這樣就只能通過同步來實現,所以他們只能在同步方法或者同步塊中被呼叫。

另外,notify()可能會引發死鎖問題,而notifyAll()不會,具體參照點選進入

==========================
下面講一個在練習的時候發現的知識點:
下面是一段生產者消費者模式程式:
Account類:

package cn.review.waitNotify.three;

public class Account {
    private String accountNo;
    private Double balance;

    public Account(String accountNo, double balance){
        this.accountNo = accountNo;
        this.balance = balance;
    }

    private boolean flag_draw;

    public String getAccountNo() {
        return accountNo;
    }

    public double getBalance() {
        return balance;
    }

    //取錢
    public synchronized void draw(double money){

        if(!flag_draw){  //如果flag_draw表名賬戶還沒人存進去
            try {
                balance.wait();   //鎖的是整個account物件
            } catch (InterruptedException e) {
                e.printStackTrace();
            }      
        }else{
            balance = balance - money;
            System.out.println(Thread.currentThread().getName()+" 取出"+money+"元,餘額是"+balance+"元");

            flag_draw = false;
            balance.notifyAll();
        }


    }
    //存錢
    public synchronized void deposit(double money){
        if(flag_draw){
            try {
                balance.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            balance = balance + money;
            System.out.println(Thread.currentThread().getName()+" 存入"+money+"元,餘額是"+balance+"元");
            flag_draw = true;
            balance.notifyAll();
        }
    }
}

生產者(存錢的人)類:

package cn.review.waitNotify.three;

public class Producer implements Runnable{
    private Account account;
    private double depositMoney;
    public Account getAccount() {
        return account;
    }

    public double getDepositMoney() {
        return depositMoney;
    }
    public void setDepositMoney(double depositMoney) {
        this.depositMoney = depositMoney;
    }

    public Producer(Account account, double depositMoney){
        this.account = account;
        this.depositMoney = depositMoney;
    }

    @Override
    public void run() {
        //進行100次存錢
        for(int i = 0; i < 100; i++){
            account.deposit(depositMoney);
        }

    }



}

消費者(取錢的人)類:

package cn.review.waitNotify.three;

public class Consumer implements Runnable{
    private Account account;
    private double drawMoney;

    public Consumer(Account account, double drawMoney){
        this.account = account;
        this.drawMoney = drawMoney;
    }

    public Account getAccount() {
        return account;
    }

    public double getDrawMoney() {
        return drawMoney;
    }

    @Override
    public void run() {
        //進行100次取款
        for(int i = 0; i < 100; i++){
            account.draw(drawMoney);
        }
    }



}   

測試類:

package cn.review.waitNotify.three;

public class Test1 {
    public static void main(String[] args) {
        Account account = new Account("212212", 1000);
        Producer p1 = new Producer(account, 100);
        Consumer c1 = new Consumer(account, 80);
        Consumer c2 = new Consumer(account, 80);

        new Thread(p1, "存錢者").start();
        new Thread(c1, "取前者1").start();
        new Thread(c2, "取前者2").start();

    }
}

測試類中另開3條執行緒,其中1條生產者,2條消費者。每個執行緒都進行100次的存錢或取錢,在Account類中保證了一個賬戶不能進行連續的存錢或者取錢,執行得到的結果是:

存錢者 存入100.0元,餘額是1100.0元
取前者2 取出80.0元,餘額是1020.0元
存錢者 存入100.0元,餘額是1120.0元
取前者1 取出80.0元,餘額是1040.0元
存錢者 存入100.0元,餘額是1140.0元
取前者2 取出80.0元,餘額是1060.0元
存錢者 存入100.0元,餘額是1160.0元
取前者1 取出80.0元,餘額是1080.0元
存錢者 存入100.0元,餘額是1180.0元
取前者2 取出80.0元,餘額是1100.0元
存錢者 存入100.0元,餘額是1200.0元
取前者1 取出80.0元,餘額是1120.0...
...

程式最後處於不輸出任何資料狀態(注意這不是死鎖)。。

以上一切都很正常,當我把Account類的取錢方法和存錢方法改為:
取錢方法:

    //取錢
    public void draw(double money){
        synchronized (balance) {
            if(!flag_draw){  //如果flag_draw表名賬戶還沒人存進去
                try {
                    balance.wait();   //鎖的是整個account物件
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }      
            }else{
                balance = balance - money;
                System.out.println(Thread.currentThread().getName()+" 取出"+money+"元,餘額是"+balance+"元");

                flag_draw = false;
                balance.notifyAll();
            }
        }
    }

存錢方法:

    //存錢
    public void deposit(double money){
        synchronized (balance) {
            if(flag_draw){
                try {
                    balance.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                balance = balance + money;
                System.out.println(Thread.currentThread().getName()+" 存入"+money+"元,餘額是"+balance+"元");
                flag_draw = true;
                balance.notifyAll();
            }
        }
    }

則會出現以下結果:

存錢者 存入100.0元,餘額是1100.0元
Exception in thread "存錢者" java.lang.IllegalMonitorStateException
    at java.lang.Object.notifyAll(Native Method)
    at cn.review.waitNotify.three.Account.deposit(Account.java:53)
    at cn.review.waitNotify.three.Producer.run(Producer.java:26)
    at java.lang.Thread.run(Thread.java:619)

百度了下,報IllegalMonitorStateException的原因是:

首先你要了解這個異常為什麼會丟擲,這個異常會在三種情況下丟擲:
1>當前執行緒不含有當前物件的鎖資源的時候,呼叫obj.wait()方法;
2>當前執行緒不含有當前物件的鎖資源的時候,呼叫obj.notify()方法。
3>當前執行緒不含有當前物件的鎖資源的時候,呼叫obj.notifyAll()方法。

後來我把balance+=money去掉了,程式又恢復正常了。原因是這樣的, 當執行緒進入臨界區時,獲得了balance的物件所,而在Account類中,balance是Double型別的,而Double型別是不可變類,它與String一樣,也就是當進行balance+=money時,已經將balance的例項改變了,再也不指向原來的Double物件了,這時候進行balance.wait()balance.notify()balance.notifyAll(); 就自然會報錯。

重點記下:Double、Integer、Character等是不可變類。wait()、notify()、notifyAll()在同步程式碼塊裡面使用,

相關文章