生產者消費者
介紹
優點
- 可以解耦生產者和消費者,互相之間不會產生影響
- 支持併發操作,生產者只管生產數據,生產的數據放到緩衝區中,而不需要等消費者消費完再生產下一個數據,不會造成阻塞
- 支持忙閒不均
數據單元
- 特性
- 關聯到業務對象
- 完整性
- 獨立性
- 顆粒度
設計
- 緩衝區一般使用阻塞隊列,當隊列滿時會阻塞生產者繼續生產數據,直到有消費者來消費了數據。當隊列爲空時也會阻塞消費者繼續消費
- 生產消費者問題是個非常典型的多線程問題,涉及到的對象包括“生產者”,“消費者”,“倉庫”,“產品”
- 關係
- 生產者只有在倉庫未滿時生產,倉滿就停止生產
- 消費者僅僅在倉庫有產品的時候才能消費,倉空就等待
- 當消費者發現倉庫沒產品消費的時候通知生產者生產
- 生產者在生成出產品後通知等待的消費者去消費
- wait/notify方法
- sleep()是Thread類的方法,而wait(),notify(),notifyAll()是Object類中定義的方法,儘管這兩個方法都會影響線程的執行行爲,但是本質上是有區別的
- Thread.sleep()不會導致鎖行爲的改變,可以簡單的認爲,與鎖相關的方法都定義在Object類中,因此調用Thread.sleep()是不會影響鎖的相關行爲
- Thread.sleep和object.wait都會暫停當前的線程,對於CPU資源來說,不管是哪種方式暫停線程,都表示它暫時不需要CPU的執行時間,OS會將執行時間分配給其他線程。區別是調用wait後,需要別的線程執行notify/notifyAll才能重新獲得CPU執行時間
實現生產者消費者模型
- BlockingQueue阻塞隊列方法
- Object的wait()/notify()方法
- Lock和Condition的await()/signal()方法
java實現
方法一:使用BlockingQueue阻塞隊列方法
數據類Data
public class Data {
private int id;
//生產量
private int num;
public Data(int id,int num){
this.id=id;
this.num=num;
}
public Data(){
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
生產者
import java.util.Random;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Producer implements Runnable {
//共享阻塞隊列
private BlockingDeque<Data> queue;
//是否還在運行
private volatile boolean isRunning = true;
//id生成器原子操作
private static AtomicInteger count = new AtomicInteger();
// 生成隨機數
private static Random random = new Random();
public Producer(BlockingDeque<Data> queue){
this.queue=queue;
}
@Override
public void run() {
try{
while(isRunning){
// 模擬生產耗時
Thread.sleep(random.nextInt(1000));
int num=count.incrementAndGet();
Data data=new Data(num,num);
System.out.println("當前>>生產者:"+Thread.currentThread().getName()+"生產量"+num);
if(!queue.offer(data,2, TimeUnit.SECONDS)){
System.out.println("生產失敗...");
}
}
}catch (Exception e){
e.printStackTrace();
}
}
public void stop(){
isRunning=false;
}
}
消費者
import java.util.Random;
import java.util.concurrent.BlockingDeque;
public class Consumer implements Runnable {
//雙端隊列,加入或者取出元素都是線程安全的
private BlockingDeque<Data> queue;
private static Random random=new Random();
public Consumer(BlockingDeque<Data> queue){
this.queue=queue;
}
@Override
public void run(){
while (true){
try{
// 檢索並刪除,如果需要等待、直到元素可用。
Data data= queue.take();
//模擬消費耗時
Thread.sleep(random.nextInt(1000));
if(data!=null){
System.out.println("當前<<消費者:"+Thread.currentThread().getName()+",消費量"+data.getNum());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
測試
import java.util.concurrent.*;
public class TestPro {
public static void main(String[] args) throws InterruptedException{
BlockingDeque<Data> queue = new LinkedBlockingDeque<>(10);
Producer producer1 = new Producer(queue);
Producer producer2 = new Producer(queue);
Producer producer3 = new Producer(queue);
Consumer consumer1 = new Consumer(queue);
Consumer consumer2 = new Consumer(queue);
Consumer consumer3 = new Consumer(queue);
ExecutorService service= Executors.newCachedThreadPool();
service.execute(producer1);
service.execute(producer2);
service.execute(producer3);
service.execute(consumer1);
service.execute(consumer2);
service.execute(consumer3);
Thread.sleep(3000);
producer1.stop();
producer2.stop();
producer3.stop();
Thread.sleep(1000);
service.shutdown();
}
}
結果
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hBNwcyGG-1582708080156)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200226105053.png)]
最後一次生產20此時所有的生產者都停止生產了,但是此時產品池還沒空,於是消費者繼續消費,直到把產品池中的數據消耗完
方法二:使用Object的wait()/notify()方法
wait()/ nofity()方法是基類Object的兩個方法,也就意味着所有Java類都會擁有這兩個方法,這樣,我們就可以爲任何對象實現同步機制。
- wait():當緩衝區已滿/空時,生產者/消費者線程停止自己的執行,放棄鎖,使自己處於等待狀態,讓其他線程執行。
- notify():當生產者/消費者向緩衝區放入/取出一個產品時,向其他等待的線程發出可執行的通知,同時放棄鎖,使自己處於等待狀態。
生產者
import java.util.Queue;
import java.util.Random;
public class Producer extends Thread {
private Queue<Integer> queue;
String name;
int maxSize;
int i=0;
public Producer(String name,Queue<Integer> queue,int maxSize){
super(name);
this.name=name;
this.queue=queue;
this.maxSize=maxSize;
}
@Override
public void run(){
while (true){
synchronized (queue){
while (queue.size()==maxSize){
try{
System.out.println("隊列已經滿了,生產者["+name+"]線程等待"+"消費者從隊列中消費產品。");
queue.wait();
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println("[" + name + "] 生產產品 : +" + i);
queue.offer(i++);
queue.notifyAll();
try{
Thread.sleep(new Random().nextInt(1000));
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
消費者
import java.util.Queue;
import java.util.Random;
public class Consumer extends Thread {
private Queue<Integer> queue;
String name;
int maxSize;
public Consumer(String name,Queue<Integer>queue,int maxSize){
super(name);
this.name=name;
this.queue=queue;
this.maxSize=maxSize;
}
@Override
public void run(){
while (true){
synchronized (queue){
while (queue.isEmpty()){
try{
System.out.println("隊列是空的 消費者[" + name + "] 等待生產者生產");
queue.wait();
}catch (Exception e){
e.printStackTrace();
}
}
int x=queue.poll();
System.out.println("[" + name + "] 消費產品 : " + x);
queue.notifyAll();
try{
Thread.sleep(new Random().nextInt(1000));
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
生產者消費者模式
import java.util.LinkedList;
import java.util.Queue;
/**
* 生產者消費者模式:使用Object.wait()/notify()方法實現
*/
public class ProdicerConsumer {
private static final int CAPACITY = 500;
public static void main(String[] args) {
Queue<Integer> queue = new LinkedList<Integer>();
Thread producer1 = new Producer("P-1", queue, CAPACITY);
Thread producer2 = new Producer("P-2", queue, CAPACITY);
Thread consumer1 = new Consumer("C1", queue, CAPACITY);
Thread consumer2 = new Consumer("C2", queue, CAPACITY);
Thread consumer3 = new Consumer("C3", queue, CAPACITY);
producer1.start();
producer2.start();
consumer1.start();
consumer2.start();
consumer3.start();
}
}
結果
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2s2ckWoo-1582708080157)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200226160351.png)]
注意要點
- 判斷Queue大小爲0或者大於queueSize時須使用
while (condition) {}
,不能使用if(condition) {}
。其中while(condition)
循環,它又被叫做**“自旋鎖”。爲防止該線程沒有收到notify()
調用也從wait()
中返回(也稱作虛假喚醒**),這個線程會重新去檢查condition條件以決定當前是否可以安全地繼續執行還是需要重新保持等待,而不是認爲線程被喚醒了就可以安全地繼續執行了。
方法三:使用Lock和Condition的await() / signal()方法
在JDK5.0之後,Java提供了更加健壯的線程處理機制,包括同步、鎖定、線程池等,它們可以實現更細粒度的線程控制。Condition接口的await()
和signal()
就是其中用來做同步的兩種方法,它們的功能基本上和Object的wait()
/ nofity()
相同,完全可以取代它們,但是它們和新引入的鎖定機制Lock
直接掛鉤,具有更大的靈活性。通過在Lock
對象上調用newCondition()
方法,將條件變量和一個鎖對象進行綁定,進而控制併發程序訪問競爭資源的安全。
生產者消費者模式:使用Lock和Condition實現
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumerByLock {
private static final int CAPACITY = 5;
private static final Lock lock = new ReentrantLock();
private static final Condition fullCondition = lock.newCondition(); //隊列滿的條件
private static final Condition emptyCondition = lock.newCondition(); //隊列空的條件
public static void main(String args[]){
Queue<Integer> queue = new LinkedList<Integer>();
Thread producer1 = new Producer("P-1", queue, CAPACITY);
Thread producer2 = new Producer("P-2", queue, CAPACITY);
Thread consumer1 = new Consumer("C1", queue, CAPACITY);
Thread consumer2 = new Consumer("C2", queue, CAPACITY);
Thread consumer3 = new Consumer("C3", queue, CAPACITY);
producer1.start();
producer2.start();
consumer1.start();
consumer2.start();
consumer3.start();
}
/**
* 生產者
*/
public static class Producer extends Thread{
private Queue<Integer> queue;
String name;
int maxSize;
int i = 0;
public Producer(String name, Queue<Integer> queue, int maxSize){
super(name);
this.name = name;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run(){
while(true){
//獲得鎖
lock.lock();
while(queue.size() == maxSize){
try {
System.out .println("隊列已經滿了,生產者["+name+"]線程等待"+"消費者從隊列中消費產品。");
//條件不滿足,生產阻塞
fullCondition.await();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
System.out.println("[" + name + "] 生產產品 : +" + i);
queue.offer(i++);
//喚醒其他所有生產者、消費者
fullCondition.signalAll();
emptyCondition.signalAll();
//釋放鎖
lock.unlock();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 消費者
*/
public static class Consumer extends Thread{
private Queue<Integer> queue;
String name;
int maxSize;
public Consumer(String name, Queue<Integer> queue, int maxSize){
super(name);
this.name = name;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run(){
while(true){
//獲得鎖
lock.lock();
while(queue.isEmpty()){
try {
System.out.println("隊列是空的 消費者[" + name + "] 等待生產者生產");
//條件不滿足,消費阻塞
emptyCondition.await();
} catch (Exception ex) {
ex.printStackTrace();
}
}
int x = queue.poll();
System.out.println("[" + name + "] 消費產品 : " + x);
//喚醒其他所有生產者、消費者
fullCondition.signalAll();
emptyCondition.signalAll();
//釋放鎖
lock.unlock();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
結果
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QNVflgMO-1582708080158)(https://raw.githubusercontent.com/iszhonghu/Picture-bed/master/img/20200226164915.png)]