生產者消費者模型
所謂的生產者消費者模型,是通過一個容器來解決生產者和消費者的強耦合問題。通俗的講,就是生產者在不斷的生產,消費者也在不斷的消費,可是消費者消費的產品是生產者生產的,這就必然存在一箇中間容器,我們可以把這個容器想象成是一個貨架,當貨架空的時候,生產者要生產產品,此時消費者在等待生產者往貨架上生產產品,而當貨架滿的時候,消費者可以從貨架上拿走商品,生產者此時等待貨架的空位,這樣不斷的循環。那麼在這個過程中,生產者和消費者是不直接接觸的,所謂的‘貨架’其實就是一個阻塞隊列,生產者生產的產品不直接給消費者消費,而是仍給阻塞隊列,這個阻塞隊列就是來解決生產者消費者的強耦合的。就是生產者消費者模型。
總結一下:生產者消費者能夠解決的問題如下:
- 生產與消費的速度不匹配
- 軟件開發過程中解耦
在具體實現生產者消費者模型之前需要先描述幾個用到的方法:
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種:
- 線程調用 sleep方法,主動放棄佔用的處理器資源。
- 線程調用了阻塞式IO方法,在該方法返回前,該線程被阻塞。
- 線程試圖獲得一個同步監視器,但該同步監視器正被其他線程所持有。
- 線程等待某個通知。
- 程序調用了 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();
}
}
}
部分結果展示: