JAVA多線程之生產者消費者模型

生產者消費者模型

所謂的生產者消費者模型,是通過一個容器來解決生產者和消費者的強耦合問題。通俗的講,就是生產者在不斷的生產,消費者也在不斷的消費,可是消費者消費的產品是生產者生產的,這就必然存在一箇中間容器,我們可以把這個容器想象成是一個貨架,當貨架空的時候,生產者要生產產品,此時消費者在等待生產者往貨架上生產產品,而當貨架滿的時候,消費者可以從貨架上拿走商品,生產者此時等待貨架的空位,這樣不斷的循環。那麼在這個過程中,生產者和消費者是不直接接觸的,所謂的‘貨架’其實就是一個阻塞隊列,生產者生產的產品不直接給消費者消費,而是仍給阻塞隊列,這個阻塞隊列就是來解決生產者消費者的強耦合的。就是生產者消費者模型。

總結一下:生產者消費者能夠解決的問題如下:

  • 生產與消費的速度不匹配
  • 軟件開發過程中解耦

在具體實現生產者消費者模型之前需要先描述幾個用到的方法:

wait()

先看一下wait()是幹什麼的?

1.wait()是Object裏面的方法,而不是Thread裏面的,這一點很容易搞錯。它的作用是將當前線程置於預執行隊列,並在wait()所在的代碼處停止,等待喚醒通知。
2.wait()只能在同步代碼塊或者同步方法中執行,如果調用wait()方法,而沒有持有適當的鎖,就會拋出異常。
wait()方法調用後悔釋放出鎖,線程與其他線程競爭重新獲取鎖。

舉個例子:

public class TestWait implements Runnable {
    private final Object object=new Object();
    @Override
    public void run() {
        synchronized (object){
        System.out.println("線程執行開始。。。");
            try {
                object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("線程執行結束。。。");
        }
    }

    public static void main(String[] args) {
        TestWait testWait=new TestWait();
        Thread thread=new Thread(testWait);
        thread.start();
    }
}

結果如下:
在這裏插入圖片描述

從結果中我們可以看出線程調用了wait()方法後一直在等待,不會繼續往下執行。這也就能解釋上面說的wait()一旦執行,除非接收到喚醒操作或者是異常中斷,否則不會繼續往下執行。

notify()方法

在上面的代碼中我們看到wait()調用以後線程一直在等待,在實際當中我們難免不希望是這樣的,那麼這個時候就用到了另一個方法notify方法:

1.notify()方法也是要在同步代碼塊或者同步方法中調用的,它的作用是使停止的線程繼續執行,調用notify()方法後,會通知那些等待當前線程對象鎖的線程,並使它們重新獲取該線程的對象鎖,如果等待線程比較多的時候,則有線程規劃器隨機挑選出一個呈wait狀態的線程。
2.notify()調用之後不會立即釋放鎖,而是當執行notify()的線程執行完成,即退出同步代碼塊或同步方法時,纔會釋放對象鎖。

還是上面的例子,剛纔我們調用了wait()方法後,線程便一直在等待,接下來我們給線程一個喚醒的信號,代碼如下:

public class TestWait implements Runnable {
    private final Object object=new Object();

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    private boolean flag=true;
    @Override
    public void run() {
        if(flag){
            this.testwait();
        }
        else {
            this.testnotify();
        }

    }
    public void testwait(){
        synchronized (object){
            try {
            System.out.println("線程開始執行。。。");
                Thread.sleep(1000);
            object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("線程執行結束。。。");
        }
    }
    public void testnotify(){
        synchronized (object){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            object.notify();
        }
    }

    public static void main(String[] args) {
        TestWait testWait=new TestWait();
        Thread thread=new Thread(testWait);
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        testWait.setFlag(false);
        Thread thread1=new Thread(testWait);
        thread1.start();
    }
}

結果如下:
在這裏插入圖片描述
我們看到在調用notify()方法之後,線程又繼續了。

notifyAll()方法

從字面意思就可以看出notifyAll是喚醒所有等待的線程。

public class TestWait implements Runnable {
    private final Object object=new Object();
    private boolean flag=true;
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        if(flag){
            this.testwait();
        }
        else {
            this.testnotify();
        }
    }
    public void testwait(){
        synchronized (object){
            try {
            System.out.println(Thread.currentThread().getName()+"線程開始執行。。。");
                Thread.sleep(1000);
            object.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"線程執行結束。。。");
        }
    }
    public void testnotify(){
        synchronized (object){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            object.notifyAll();
        }
    }
    public static void main(String[] args) {
        TestWait testWait=new TestWait();
        Thread thread=new Thread(testWait,"線程1");
        thread.start();
        Thread thread1=new Thread(testWait,"線程2");
        thread1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        testWait.setFlag(false);
        Thread thread2=new Thread(testWait);
        thread2.start();

    }
}

結果如下:
在這裏插入圖片描述
可見notifyAll()方法確實喚醒了所有等待的線程。

小結

出現阻塞的情況大體分爲如下5種:

  1. 線程調用 sleep方法,主動放棄佔用的處理器資源。
  2. 線程調用了阻塞式IO方法,在該方法返回前,該線程被阻塞。
  3. 線程試圖獲得一個同步監視器,但該同步監視器正被其他線程所持有。
  4. 線程等待某個通知。
  5. 程序調用了 suspend方法將該線程掛起。此方法容易導致死鎖,儘量避免使用該方法。

run()方法運行結束後進入銷燬階段,整個線程執行完畢。

每個鎖對象都有兩個隊列,一個是就緒隊列,一個是阻塞隊列。就緒隊列存儲了將要獲得鎖的線程,阻塞隊列存儲了被阻塞的線程。一個線程被喚醒後,纔會進入就緒隊列,等待CPU的調度;反之,一個線程被wait後,就會進入阻塞隊列,等待下一次被喚醒。

生產者消費者模型代碼示例

商品類

public class Goods {
    private int id;
    private String name;

    public Goods(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

生產者類

public class Producer implements Runnable {
    private Goods goods;

    @Override
    public void run() {
            while (true) {
                try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        synchronized (TestPC.queue) {

                goods=new Goods(1,"商品");
                if (TestPC.queue.size()<MAX_POOL) {
                    TestPC.queue.add(goods);
                    System.out.println(Thread.currentThread().getName()+"生產商品");

                } else {
                    try {
                        TestPC.queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

消費者類

public class Consumer implements Runnable {
    @Override
    public void run() {
        while (true){
            try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (TestPC.queue){

            if(!TestPC.queue.isEmpty()){
                TestPC.queue.poll();
                System.out.println(Thread.currentThread().getName()+"消費商品");
            }
            else {
                TestPC.queue.notify();
            }
        }
    }
    }
}

測試類

public class TestPC {
    public static final int MAX_POOL=10;
    public static final int MAX_PRODUCER=5;
    public static final int MAX_CONSUMER=4;
    public static  Queue<Goods> queue=new ArrayBlockingQueue<>(MAX_POOL);
    public static void main(String[] args) {
        Producer producer=new Producer();
        Consumer consumer=new Consumer();
        for(int i=0;i<MAX_PRODUCER;i++) {
            Thread threadA = new Thread(producer, "生產者線程"+i);
            threadA.start();
        }
        for(int j=0;j<MAX_CONSUMER;j++) {
            Thread threadB = new Thread(consumer, "消費者線程"+j);
            threadB.start();
        }
    }
}

部分結果展示:
在這裏插入圖片描述

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