這次討論的是Java的BlockingQueue
,java.util.concurrent.BlockingQueue
是一個Java的隊列接口,支持一系列操作,比如,在獲取和移除對象的時候如果隊列爲空會來等待隊列變成非空的,而當隊列滿了的時候,插入元素會等待隊列中的元素被移除,保證隊列有空餘的空間。
Java的BlockingQueue
是不能接受null
的值,如果傳入null
將會跑出NullPointerException
的。
如果看到JDK的源碼的話,其中會包含一個非空檢查的方法
/** * Throws NullPointerException if argument is null. * * @param v the element */ private static void checkNotNull(Object v) { if (v == null) throw new NullPointerException(); }
所以一旦傳入
null
,就會拋出NullPointerException
Java的BlockingQueue
的實現類都是線程安全的。所有的方法都是原子性的,用得都是內部鎖或者是其他的併發控制機制。
Java的BlockingQueue
接口是Java集合框架的一部分,可以用來實現生產者消費者的問題。開發者不需要擔心隊列空間是否足夠生產者或者是隊列中的對象是否可以讓消費者來進行操作,因爲這些實現都由BlockingQuene
的實現來完成的。
Java提供了幾種不同的BlockingQueue
的實現,比如ArrayBlockingQueue
,LinkedBlockingQueue
,PriorityBlockingQueue
,SynchronousQueue
等等。
我們將通過ArrayBlockingQueue
來實現生產者消費者的問題。下面就是BlockingQueue
的一些開發者需要知道的方法。
put(E e)
:這個方法用來元素插入到隊列之中。如果隊列滿了,那麼該操作將會阻塞,等待隊列重新有空間。E take()
:這個方法將從一個隊列中獲取並從隊列頭部移除出去。如果隊列是空的,它會等待隊列中的元素重新可以獲取爲止。
下面我們就可以通過阻塞隊列來實現生產者消費者問題。
Java阻塞隊列舉例——消息
一個普通的Java對象可以由生產者添加到隊列中,開發者也可以稱之爲裝在或者是隊列消息。
package com.sapphire.concurrency;
public class Message {
private String msg;
public Message(String str){
this.msg=str;
}
public String getMsg() {
return msg;
}
}
Java阻塞隊列舉例——生產者
生產者的類將會創建消息,並且將之放到隊列中,參考下面的代碼:
package com.sapphire.concurrency;
import java.util.concurrent.BlockingQueue;
public class Producer implements Runnable {
private BlockingQueue<Message> queue;
public Producer(BlockingQueue<Message> q){
this.queue=q;
}
@Override
public void run() {
//produce messages
for(int i=0; i<100; i++){
Message msg = new Message(""+i);
try {
Thread.sleep(i);
queue.put(msg);
System.out.println("Produced "+msg.getMsg());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//adding exit message
Message msg = new Message("exit");
try {
queue.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
可以看得出來,run
方法會將消息插入到阻塞隊列之中。
Java阻塞隊列舉例——消費者
消費者類將會從隊列中處理消息,並且當收到exit
消息的時候結束,參考如下代碼:
package com.sapphire.concurrency;
import java.util.concurrent.BlockingQueue;
public class Consumer implements Runnable{
private BlockingQueue<Message> queue;
public Consumer(BlockingQueue<Message> q){
this.queue=q;
}
@Override
public void run() {
try{
Message msg;
//consuming messages until exit message is received
while((msg = queue.take()).getMsg() !="exit"){
Thread.sleep(10);
System.out.println("Consumed "+msg.getMsg());
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
而生產者的run
方法會將阻塞隊列中的元素拿出來。
Java阻塞隊列舉例——服務
最後,我們爲生產者和消費者創建了阻塞隊列服務。這個生產生消費者服務將會創建固定長度的阻塞隊列,並由生產者和消費者所共用。這個服務將同時啓動生產者和消費者線程,代碼如下:
package com.sapphire.concurrency;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ProducerConsumerService {
public static void main(String[] args) {
//Creating BlockingQueue of size 10
BlockingQueue<Message> queue = new ArrayBlockingQueue<>(10);
Producer producer = new Producer(queue);
Consumer consumer = new Consumer(queue);
//starting producer to produce messages in queue
new Thread(producer).start();
//starting consumer to consume messages from queue
new Thread(consumer).start();
System.out.println("Producer and Consumer has been started");
}
}
輸出如下:
roducer and Consumer has been started
Produced 0
Produced 1
Produced 2
Produced 3
Produced 4
Consumed 0
Produced 5
Consumed 1
Produced 6
Produced 7
Consumed 2
Produced 8
...
其中的Thread.sleep
方法用來讓生產者和消費者能夠進行延時性的生產和消費message對象。
例子中使用的方法爲
put(E e)
方法,其實在BlockingQueue
中針對將元素加入隊列的方法是有三個的
1.void put(E e)
:當阻塞隊列是限制長度的阻塞隊列時,如果隊列已滿,put
方法是會持續等待,直到隊列有空餘的位置的。所以這個方法其實是一個阻塞的方法。如上面的代碼,當隊列的長度我們限制爲10的時候,如果消費者沒有及時消費掉message對象,那麼生產者的線程會在執行put的時候持續阻塞掉。
2.boolean add(E e)
:該方法和put
方法的不同之處是該方法是非阻塞的,當無法將元素插入到隊列中時,會拋出IlleaglaStateException
。
3.boolean offer(E e)
:該方法其實基本和add
方法是一致的,但是當隊列是滿的時候,offer
方法返回的是false
,而add
方法是拋出異常。
事實上,在ArrayBlockingQueue
中add
實現就是調用offer
的,當offer
返回false
的時候直接拋出異常。