Java等待喚醒機制深入理解

來源:互聯網
上載者:User

昨天寫了關於多線程引發的並發問題及解決方案—鎖,然後咱們今天繼續…

一、並發分析

Java中有個概念,叫生產者與消費者。

假設生產者不停輪流生產蘋果和香蕉,其中蘋果的價格是10元/kg,香蕉的價格是5元/kg,消費者則是對生產的商品進行購買,顯然,這裡的生產者生產的商品和消費者消費的商品是同一個商品。
定義產品類,生產者類,消費者類,因為需要共用商品,採取的策略是主方法中建立產品類對象,並將其作為參數傳入生產者和消費者的建構函式中。


//產品類
class Product{
    String name;  //名字
    double price;  //價格
}


//生產者
class Producer extends Thread{
    
    Product  p ;    //產品
    
    public Producer(Product p) {
        this.p  = p ;
    } 
    
    @Override
    public void run() {
        int i = 0 ;
        while(true){
            if(i%2==0){
                 p.name = "蘋果";
                 p.price = 10;
             }else{
                 p.name="香蕉";
                 p.price = 5;
             }
            System.out.println("生產者生產出了:"+ p.name+" 價格是:"+ p.price);
            i++;           
        } 
      }
    }
}


//消費者
class Customer extends Thread{
    
    Product p;
    
    public  Customer(Product p) {
        this.p = p;
    }
        
    @Override
    public void run() {
        while(true){
                    System.out.println("消費者消費了"+p.name+" 價格:"+ p.price);
        } 
    }
}

//測試類別
public class Demo5 {
    
    public static void main(String[] args) {
        Product p = new Product();  //產品
        //建立生產對象
        Producer producer = new Producer(p);
        //建立消費者
        Customer customer = new Customer(p);
        //調用start方法開啟線程
        producer.start();
        customer.start();
    } 
}

觀察代碼,我們會發現兩個問題

1.沒有生產出商品,消費者也來消費了

2.商品的價格發生了錯亂
瞭解線程以及其安全問題的話,這兩個問題並不難分析。

二、需求分析

顯然,我們需要生產者每生產一個就進入等待,喚醒消費者消費,消費者消費一個,消費完後進入等待,喚醒生產者繼續生產,這樣的話,必須要等待生產者徹底生產完畢,消費者才能進行商品的消費,且價格不會發生錯亂。java提供了wait()和notify()|notifyAll()(喚醒所有鎖對象為標識符的線程池中所有的線程)方法解決這個問題,即等待喚醒機制。

//產品類
class Product{
    String name;  //名字
    double price;  //價格
    boolean flag = false; //產品是否生產完畢的標識,預設情況是沒有生產完成。
}


//生產者
class Producer extends Thread{
    
    Product  p ;    //產品
    
    public Producer(Product p) {
        this.p  = p ;
    }
 
    @Override
    public void run() {
        int i = 0 ;
        while(true){
         synchronized (p) {
            if(p.flag==false){
                 if(i%2==0){
                     p.name = "蘋果";
                     p.price = 10;
                 }else{
                     p.name="香蕉";
                     p.price = 5;
                 }
                 System.out.println("生產者生產出了:"+ p.name+" 價格是:"+ p.price);
                 p.flag = true;
                 i++;
                 p.notifyAll(); //喚醒消費者去消費
            }else{
                //已經生產 完畢,等待消費者先去消費
                try {
                    p.wait();   //生產者等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }  
         }
        } 
    }
}


//消費者
class Customer extends Thread{
    
    Product p;
    
    public  Customer(Product p) {
        this.p = p;
    }
        
    @Override
    public void run() {
        while(true){
            synchronized (p) {
                if(p.flag==true){  //產品已經生產完畢
                    System.out.println("消費者消費了"+p.name+" 價格:"+ p.price);
                    p.flag = false;
                    p.notifyAll(); // 喚醒生產者去生產
                }else{
                    //產品還沒有生產,應該 等待生產者先生產。
                    try {
                        p.wait(); //消費者也等待了...
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        } 
    }
}


//測試類別
public class Demo5 {
    
    public static void main(String[] args) {
        Product p = new Product();  //產品
        //建立生產對象
        Producer producer = new Producer(p);
        //建立消費者
        Customer customer = new Customer(p);
        //調用start方法開啟線程
        producer.start();
        customer.start();
    } 
}

程式碼分析:假設生產者拿到鎖對象,此時flag為false,生產者生產出蘋果對象,flag至為true,此時喚醒消費者消費,然而,在以鎖對象p為標識符的線程池中並沒有消費者,此時生產者繼續持有鎖對象,然後發現flag為true,執行wait()方法,此時生產者釋放鎖對象p,並進入以p為標識符的線程池中等待,此時消費者擷取鎖,flag為true,消費完畢喚醒以鎖對象p為線程池中的等待線程當中的其中一個線程(無法指定喚醒線程),此時生產者被喚醒,此時消費者假設還是持有cpu執行權,繼續迴圈,發現flag為false,進入wait(),釋放鎖,生產者擷取cpu執行權,開始生產….

三、wait()和notify()的使用注意點

1. wait方法與notify方法是屬於Object對象的。
理由:若不是Object對象,那麼調用者對象即鎖對象則必須是線程對象嗎?顯然不必,鎖對象是任意對象。
2. wait方法與notify方法必須要在同步代碼塊或者是同步函數中才能使用。
理由:如果不存在同步代碼塊和同步函數則沒有鎖對象這個概念,而這兩個方法必須由鎖對象調用。
3. wait方法與notify方法必需要由鎖對象調用。
理由:因為需要以鎖對象為標識符建立一個線程池,生產者與消費者必須用同一個鎖,不同的鎖會建立不同的線程池。且不用鎖對象調用是報錯的,要求就是以鎖對象調用,否則就會報錯。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.