多線程經典消費者實例

需求

我們的程序想要實現這樣的一個功能,兩個線程,一個不斷往一個容器加數據,一個不斷從這個容器取數據。

設計的問題

我們的第一個問題是,如果容器滿了怎麼辦,空了又怎麼。解決的辦法是使用wait()和notify()。思路是當容器滿或空時,對應的線程就應該停下了,等到不空或者不滿的時候再繼續。顯然wait()和notify()可以很好的實現。當空時,暫停取,使用wait(),添加線程添加了後就不空了,就可以使用notify()喚醒取的線程了。滿的時候也是一樣的。

sleep()和wait()的區別

暫停也可用sleep(),爲什麼不用sleep()呢?首先是我們不知道停多久,有notify()配合使用才方便。sleep()和wait()有一個重要的區別是,sleep()不會讓出對象鎖。我們寫個例子來證明下:

class Lock{
    public synchronized void printlnTest(){
        for(int i=0;i<100;i++)
        System.out.println(Thread.currentThread().getName());
    }
    public synchronized void sleepTest(){

        System.out.println("sleeped 2 second");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

class Print implements Runnable{
    private Lock lock;
    public  Print(Lock lock){
        this.lock = lock;
    }
    @Override
    public void run(){
        lock.printlnTest();
    }
}

class SleepTest implements Runnable{
    private Lock lock;
    public  SleepTest(Lock lock){
        this.lock = lock;
    }
    @Override
    public void run(){
        lock.sleepTest();
    }
}
public class SleepAndWait {

    public static void main(String[] args) {
        Lock lock = new Lock();
        Print print = new Print(lock);
        SleepTest sleep = new SleepTest(lock);

        new Thread(sleep).start();
        new Thread(print).start();  
    }
}

輸出的結果是,輸出了sleeped 2 second,等待兩秒後,纔可以看到輸出的100個Thread-1。如果把synchronized去掉,也就是不加鎖,Thread-1會立即輸出不會等兩秒。這裏證明sleep是不會讓出對象鎖的。

非常有用的線程圖

這裏寫圖片描述

消費者實例

這是容器,使用一個類似棧的容器

public class Stack {
    private char[] container  = new char[6];
    private int top;//指向棧頂元素的上一個

    public synchronized void push(char a){

        if(top==container.length){//if 改爲while更合適,思考下爲什麼
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
            this.notify();
            container[top]=a;
            top++;
            System.out.println(Thread.currentThread().getName()+"入棧的元素"+a);

    }

    public synchronized char pop(){

        if(top==0){//if 改爲while更合適,思考下爲什麼
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
            this.notify();
            top--;
            System.out.println(Thread.currentThread().getName()+"出棧的元素:"+container[top]);
            return container[top];

    }

}

生產者

public class Producer implements Runnable{
    private Stack stack;

    public Producer(Stack stack){
        this.stack = stack;
    }

    @Override
    public void run(){

        for(int i=0;i<10;i++){

            stack.push((char)('a'+i));
        }

    }
}

消費者

public class Consumer implements Runnable{

    private Stack stack;
    public Consumer(Stack stack){
        this.stack = stack;
    }

    @Override
    public void run(){

        try {
            for(int i=0;i<10;i++)
            stack.pop();

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }       
    }
}

主線程

public class ThreadTest {

    public static void main(String[] args) {

        Stack stack = new Stack();
        Producer p = new Producer(stack);
        Consumer c = new Consumer(stack);
        Thread t1 = new Thread(p);
        Thread t2 = new Thread(c);

        t1.start();
        t2.start();
    }
}

同步的問題

當添加線程的push方法和取線程的pop方法,不是原子操作時意思就是push方法執行一半,cpu切換給pop方法的線程執行,就會出現問題。比如說,當執行了container[top]=a;但是還沒執行top++;切換到取線程時,把原來棧頂的元素取出來了並且指針向下減,如果再返回添加線程,繼續執行top++;結果就是新添加的元素丟失了,原來棧頂的元素取了一次還在。所以pop方法和push方法要加鎖,作爲原子操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章