天天看點

多線程實踐-生産者消費者

        當多個線程操作同一個資源,但是操作的動作不同時,就會需要線程間進行通信。很著名的是生産者消費者的例子。

        有一個盤子,隻能放一片面包,生産者生産面包放入盤子,消費者從盤子中取走面包吃掉。

        由簡單開始,i+1。先看一個生産者、一個消費者。

代碼如下:

public class ProducerConsumerDemo {
    public static void main(String[] args){
        Resource r = new Resource();

        new Thread(new Producer(r)).start();
        new Thread(new Consumer(r)).start();
    }
}

class Resource{  //公共資源
    private String name;
    private int count =1;
    private boolean flag = false;

    public synchronized void set(String name){
        if(flag)
            try{this.wait();}catch(Exception e){}
        this.name = name + "--" + count++;
        System.out.println(Thread.currentThread().getName() + "...生産者:生産了"+this.name);
        flag = true;
        notify();
    }
    public synchronized void out(){
        if(!flag)
            try{this.wait();}catch(Exception e){}
        System.out.println(Thread.currentThread().getName() + "...消費者:消費了"+this.name);
        flag = false;
        notify();
    }
}
class Producer implements Runnable{
    private Resource res;
    Producer(Resource res){
        this.res = res;
    }
    public void run(){
        while(true){
            res.set("面包");
        }
    }
}
class Consumer implements Runnable{
    private Resource res;
    Consumer(Resource res){
        this.res = res;
    }
    public void run(){
        while(true){
            res.out();
        }
    }
}
           

        運作結果如圖:

多線程實踐-生産者消費者

        由運作結果可以看到。Thread-0和Tread-1兩個線程是交替進行,生産者生産商品i,消費者就把商品i消費掉。然後生産者生産商品i+1,消費者再消費商品i+1。

        本來我自己想的實作過程是這樣的:對于盤子來說,它有兩種狀态,可以往裡放面包和不可以放面包。生産者來了,如果可放,就生産一個面包,并把盤子置為不可放面包的狀态,如果不可放,就什麼都不操作。消費者來了,如果可放(就代表不能取),就什麼都不操作,如果不可放(代表能取),就取走一個面包,并把盤子置為可放面包狀态。代碼如下:

class Resource{  //公共資源
    private String name;
    private int count =1;
    private boolean flag = false;

    public synchronized void set(String name){
        if(flag)
        {
        }
          //try{this.wait();}catch(Exception e){}
        else{
            this.name = name + "--" + count++;
            System.out.println(Thread.currentThread().getName() + "...生産者:生産了"+this.name);
            flag = true;
        }
        //notify();
    }
    public synchronized void out(){
        if(!flag){
        }
        //try{this.wait();}catch(Exception e){}
        else{
            System.out.println(Thread.currentThread().getName() + "...消費者:消費了"+this.name);
            flag = false;
        }

        //notify();
    }
}
           

        與上面的示例中代碼的差別是沒有使用wait()和notify()方法。一樣能實作效果。看圖:

多線程實踐-生産者消費者

        不用使用wait()和notify()與使用有什麼差別呢?既然它存在,肯定是有它的道理的。猜測它的優點是效率更高。用什麼方法可以驗證一下?嘗試着加了運作時間和列印輸出。如圖:

        (1)不用wait()/notify()

多線程實踐-生産者消費者

        (2)用wait()/notify()

多線程實踐-生産者消費者

        count為10000時,不使用wait()和notify()生産9999個面包需要1330ms,而使用wait()和notify()生産9999個面包隻需要406ms。多次執行,每次的結果相差不大。

增加一下數量級,再比比,也很明顯:6704msVS 3208ms。

多線程實踐-生産者消費者
多線程實踐-生産者消費者

(1)不用wait()/notify()(2)用wait()/notify()                   

        計時代碼增加到了main方法所在類和公共資源類下,代碼如下:

public class ProducerConsumerDemo {
    public static long strateTime;
    public static void main(String[] args){
        Resource r = new Resource();
        strateTime = System.currentTimeMillis();
        new Thread(new Producer(r)).start();
        new Thread(new Consumer(r)).start();
    }
}

class Resource{  //公共資源
    public static long endTime;
    private String name;
    private int count =1;
    private boolean flag = false;

    public synchronized void set(String name){
        if(flag)
//        {
//           System.out.println("無效執行");
//        }
            try{this.wait();}catch(Exception e){}
        else{
            this.name = name + "--" + count++;
            if(count==100000){
                endTime= System.currentTimeMillis();
                System.out.println("程式運作時間:" + ( endTime - ProducerConsumerDemo.strateTime )+"ms");
            }
            System.out.println(Thread.currentThread().getName() + "...生産者:生産了"+this.name);
            flag = true;
        }
        notify();
    }
    public synchronized void out(){
        if(!flag)
//        {
//            System.out.println("無效執行");
//        }
            try{this.wait();}catch(Exception e){}
        else{
            System.out.println(Thread.currentThread().getName() + "...消費者:消費了"+this.name);
            flag = false;
        }

        notify();
    }
}
           

        對比發現,差不多兩倍的差距,效率是不一樣的。是因為啥呢?

        //TODO:

多線程方法彙總:

        等待喚醒:wait()、notify()、notifyAll()。

        waite()是把線程由運作狀态置為等待狀态,等待線程都存線上程池中。

        notify()方法是把等待狀态的線程從線程池中喚醒。通常喚醒線程池中的第一個等待的線程。

        notifyAll()是把線程池中的所有等待線程都喚醒。

繼續閱讀