一,Java中三種實現生產者消費者
1,使用wait()/notify()的方式
2,使用J.U.C下Condition的await()/signal()的方式實現
3,使用阻塞隊列實現
注:這篇博文主要將使用阻塞隊列實現,至於前面的兩種可以看看我的另外一篇博客
二,什麼是阻塞隊列,阻塞隊列的特性
1,一個支持兩個附加操作的隊列。這兩個附加的操作支持阻塞的插入和移除方法。
2,Java針對阻塞隊列的操作方式有不同的處理邏輯
三,阻塞隊列的實現原理
1,推薦幾篇博客:Java併發講解
2,對於Java併發的學習,切不可知道幾個關鍵字就可以了,要把J.U.C下的包逐個擊破,瞭解其原理及其使用場景纔是學習的關鍵。
四,阻塞隊列有哪些
1,ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列
2,LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列。
3,PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。
4,DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
5,SynchronousQueue:一個不存儲元素的阻塞隊
6,LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列
7,LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列
總結,對於無界的隊列,值得注意的是,在線程池的使用中,如果請求過多,就會造成OOM.很多隊列都作爲線程池的等待隊列來使用,以滿足不同的處理需求,具體看我推薦的博文
五,使用阻塞隊列來實現生產消費
1,需求模型如下,該圖來自《Java併發編程藝術》
2,具體實現並非copy(因爲爲不全)
3,簡單理解:大致分三步
1),生產者生產數據,將數據放入阻塞隊列,實際場景,來自客戶段的請求之類的
2),消費者1從阻塞隊列中拿取數據,當阻塞隊列中沒有數據時,阻塞隊列就會調用LockSupport.park()使調用take()的線程阻塞,那消費者1 就會在那等着,如果遇到異常,該線程會被中斷。
3),消費者1 的工作並非是處理這些數據,而是將他們分發給三個阻塞隊列,根據得到的數據採用哈希算法,算出這條數據應該去那個隊列,那條去那個隊列。(不太清楚書上是怎麼具體實現的,但是思想都是一樣的,使這些數據均勻的分佈到三個隊列)
4)剩下的消費者就是真正處理數據 的了(這裏我簡但的做了個打印);
六,代碼實現
1,封裝數據
package 多線程實戰.生產者消費者;
/**
* 消息數據 待處理的數據
* 假設保存個人數據
*/
public class MessageData {
private String name;
private int id;
public MessageData(){
}
public MessageData(String name,int age){
this.id = age;
this.name = name;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "["+name+"]";
}
}
2,封裝阻塞隊列,這裏我使用的是ArrayBlockingQueue,也可以採用別的,效率可能會更好
package 多線程實戰.生產者消費者;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
*
*/
public class MessageTaskQueue{
public final BlockingQueue<MessageData> queue;
public MessageTaskQueue(int size){
queue = new ArrayBlockingQueue<>(size);
}
public void put(MessageData data){
try {
queue.offer(data);
}catch (Exception e){
Thread.currentThread().interrupt();
}
}
public MessageData get(){
try {
return queue.take();
}catch (Exception e){
Thread.currentThread().interrupt();
}
return null;
}
}
3,創建生產者線程,用於生產數據並把數據放入阻塞隊列
package 多線程實戰.生產者消費者;
public class MessageProducer implements Runnable{
private MessageTaskQueue queue;
private MessageData data;
MessageProducer(MessageTaskQueue queue){
this.queue = queue;
}
@Override
public void run() {
for (;;){
data = new MessageData("LYY-"+System.currentTimeMillis()%9+"["+Thread.currentThread().getName()+"]",1);
System.out.println("生產消息···"+data.toString());
queue.put(data);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
這裏的書寫可能不太完美
4,創建消息分發的線程,我用一個hashmap保存者阻塞隊列id和阻塞隊列的關係,
package 多線程實戰.生產者消費者;
import java.util.HashMap;
public class DispatchMessageTask implements Runnable{
private MessageTaskQueue queue;
private HashMap<Integer,MessageTaskQueue> hashMap;
public DispatchMessageTask(MessageTaskQueue queue,HashMap hashMap){
this.hashMap = hashMap;
this.queue = queue;
}
@Override
public void run() {
for (;;){
try {
MessageData data = queue.get();
//這裏就是所謂的哈希算法,System.nanoTime()這個函數產生的值
//就是哈希值,對size取模運算,會得到0,1,2這三個數,他們一一對應一個隊列
int index = (int) (System.nanoTime() % hashMap.size());
MessageTaskQueue messageTaskQueue = hashMap.get(index);
System.out.println("消息分發···"+index+" 號隊列獲得消息: "+data.toString());
messageTaskQueue.put(data);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5,創建消費者線程
package 多線程實戰.生產者消費者;
public class MessageConsumer implements Runnable{
private MessageTaskQueue queue;
MessageConsumer(MessageTaskQueue queue){
this.queue=queue;
}
@Override
public void run() {
for (;;){
MessageData data = queue.get();
//拿到數據就對數據進行處理
System.out.println("消費消息···"+data.toString());
}
}
}
至於爲什麼是死循環,因爲要一直拿數據,
6,啓動類
package 多線程實戰.生產者消費者;
import java.util.HashMap;
public class Run {
public static void main(String[] args) throws Exception{
//四個阻塞隊列
MessageTaskQueue queue_1 = new MessageTaskQueue(100);
MessageTaskQueue queue_2 = new MessageTaskQueue(10);
MessageTaskQueue queue_3 = new MessageTaskQueue(10);
MessageTaskQueue queue_4 = new MessageTaskQueue(10);
//用於負載均衡
HashMap<Integer,MessageTaskQueue> map = new HashMap<>();
map.put(0,queue_2);
map.put(1,queue_3);
map.put(2,queue_4);
//兩個線程生產消息
MessageProducer producer = new MessageProducer(queue_1);
Thread producer_Thread_1 = new Thread(producer);
Thread producer_Thread_2 = new Thread(producer);
Thread producer_Thread_3 = new Thread(producer);
//消息分發
DispatchMessageTask dispatchMessageTask = new DispatchMessageTask(queue_1,map);
Thread dispach_Thread = new Thread(dispatchMessageTask);
//消費消息
MessageConsumer consumer = new MessageConsumer(queue_2);
MessageConsumer consumer1 = new MessageConsumer(queue_3);
MessageConsumer consumer2 = new MessageConsumer(queue_4);
Thread c1_Thread = new Thread(consumer);
Thread c2_Thread = new Thread(consumer1);
Thread c3_Thread = new Thread(consumer2);
//開啓線程
producer_Thread_1.start();
producer_Thread_2.start();
producer_Thread_3.start();
Thread.sleep(1000);
dispach_Thread.start();
c1_Thread.start();
c2_Thread.start();
c3_Thread.start();
}
}
用兩個線程來生產,數據還是很快被分發消費,自己實現可多加幾個,或者把休眠時間調短一點,
運行截圖
這是一個全過程,可以適當調整阻塞隊列的大小,觀察隊列阻塞的情況。。
參看:《Java併發編程藝術》