java多線程-阻塞隊列實現消費者生產者

一,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併發編程藝術》

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