如果BlockQueue是空的,從BlockingQueue取東西的操作將會被阻斷進入等待狀態,直到BlockingQueue進了東西纔會被喚醒.同樣,如果BlockingQueue是滿的,任何試圖往裏存東西的操作也會被阻斷進入等待狀態,直到BlockingQueue裏有空間纔會被喚醒繼續操作.
1.BlockingQueue定義的常用方法如下
1)add(anObject)
把anObject加到BlockingQueue裏,即如果BlockingQueue可以容納,則返回true,否則報異常
2)offer(anObject)
表示如果可能的話,將anObject加到BlockingQueue裏,即如果BlockingQueue可以容納,則返回true,否則返回false.
3)put(anObject)
把anObject加到BlockingQueue裏,如果BlockQueue沒有空間,則調用此方法的線程被阻斷直到BlockingQueue裏面有空間再繼續.
4)poll(time)
取走BlockingQueue裏排在首位的對象,若不能立即取出,則可以等time參數規定的時間,取不到時返回null
5)take()
取走BlockingQueue裏排在首位的對象,若BlockingQueue爲空,阻斷進入等待狀態直到Blocking有新的對象被加入爲止
6)drainTo()
一次性從BlockingQueue獲取所有可用的數據對象(還可以指定獲取數據的個數), 通過該方法,可以提升獲取數據效率;不需要多次分批加鎖或釋放鎖。
- | Throws Exception | Special Value | Blocks | Times Out |
---|---|---|---|---|
Insert | add(o) | offer(o) | put(o) | offer(o, timeout, timeunit) |
Remove | remove(o) | poll() | take() | poll(timeout, timeunit) |
Examine | element() | peek() |
這四套方法對應的特點分別是:
1. ThrowsException:如果操作不能馬上進行,則拋出異常
2. SpecialValue:如果操作不能馬上進行,將會返回一個特殊的值,一般是true或者false
3. Blocks:如果操作不能馬上進行,操作會被阻塞
4. TimesOut:如果操作不能馬上進行,操作會被阻塞指定的時間,如果指定時間沒執行,則返回一個特殊值,一般是true或者false
2.BlockingQueue具體的實現類,根據不同需求,選擇不同的實現類
1)ArrayBlockingQueue
規定大小的BlockingQueue,其構造函數必須帶一個int參數來指明其大小.其所含的對象是以FIFO(先入先出)順序排序的.
BlockingQueue queue = new ArrayBlockingQueue(1024);
queue.put("1");
Object object = queue.take();
2)LinkedBlockingQueue
大小不定的BlockingQueue,若其構造函數帶一個規定大小的參數,生成的BlockingQueue有大小限制,若不帶大小參數,所生成的BlockingQueue的大小由Integer.MAX_VALUE來決定.
和ArrayBlockingQueue一樣,LinkedBlockingQueue 也是以先進先出的方式存儲數據,最新插入的對象是尾部,最新移出的對象是頭部。下面是一個初始化和使LinkedBlockingQueue的例子:
BlockingQueue<String> unbounded = new LinkedBlockingQueue<String>();
BlockingQueue<String> bounded = new LinkedBlockingQueue<String>(1024);
bounded.put("Value");
String value = bounded.take();
ArrayBlockingQueue和LinkedBlockingQueue是兩個最普通也是最常用的阻塞隊列,一般情況下,在處理多線程間的生產者消費者問題,使用這兩個類足以。
生產者消費者實例:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public class BlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
// 聲明一個容量爲10的緩存隊列
BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10);
Producer producer1 = new Producer(queue);
Producer producer2 = new Producer(queue);
Producer producer3 = new Producer(queue);
Consumer consumer = new Consumer(queue);
// 藉助Executors
ExecutorService service = Executors.newCachedThreadPool();
// 啓動線程
service.execute(producer1);
service.execute(producer2);
service.execute(producer3);
service.execute(consumer);
// 執行10s
Thread.sleep(10 * 1000);
producer1.stop();
producer2.stop();
producer3.stop();
Thread.sleep(2000);
// 退出Executor
service.shutdown();
}
}
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 生產者
*/
public class Producer implements Runnable {
private volatile boolean isRunning = true;
private BlockingQueue queue;
private static AtomicInteger count = new AtomicInteger();
private static final int DEFAULT_RANGE_FOR_SLEEP = 1000;
public Producer(BlockingQueue queue) {
this.queue = queue;
}
public void run() {
String data = null;
Random r = new Random();
System.out.println("啓動生產者線程!");
try {
while (isRunning) {
System.out.println("正在生產數據...");
Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));
data = "data:" + count.incrementAndGet();
System.out.println("將數據:" + data + "放入隊列...");
if (!queue.offer(data, 2, TimeUnit.SECONDS)) {
System.out.println("放入數據失敗:" + data);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
System.out.println("退出生產者線程!");
}
}
public void stop() {
isRunning = false;
}
}
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 消費者
*/
public class Consumer implements Runnable {
private BlockingQueue<String> queue;
private static final int DEFAULT_RANGE_FOR_SLEEP = 1000;
public Consumer(BlockingQueue<String> queue) {
this.queue = queue;
}
public void run() {
System.out.println("啓動消費者線程!");
Random r = new Random();
boolean isRunning = true;
try {
while (isRunning) {
System.out.println("正從隊列獲取數據...");
String data = queue.poll(2, TimeUnit.SECONDS);
if (null != data) {
System.out.println("拿到數據:" + data);
System.out.println("正在消費數據:" + data);
Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));
} else {
// 超過2s還沒數據,認爲所有生產線程都已經退出,自動退出消費線程。
isRunning = false;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
System.out.println("退出消費者線程!");
}
}
}
3)DelayQueue
DelayQueue中的元素只有當其指定的延遲時間到了,才能夠從隊列中獲取到該元素。DelayQueue是一個沒有大小限制的隊列,因此往隊列中插入數據的操作(生產者)永遠不會被阻塞,而只有獲取數據的操作(消費者)纔會被阻塞。
使用場景:DelayQueue使用場景較少,但都相當巧妙,常見的例子比如使用一個DelayQueue來管理一個超時未響應的連接隊列。
DelayQueue阻塞的是其內部元素,DelayQueue中的元素必須實現 java.util.concurrent.Delayed接口,這個接口的定義非常簡單:
public interface Delayed extends Comparable<Delayed> {
long getDelay(TimeUnit unit);
}
getDelay()方法的返回值就是隊列元素被釋放前的保持時間,如果返回0或者一個負值,就意味着該元素已經到期需要被釋放,此時DelayedQueue會通過其take()方法釋放此對象。
從上面Delayed 接口定義可以看到,它還繼承了Comparable接口,這是因爲DelayedQueue中的元素需要進行排序,一般情況,我們都是按元素過期時間的優先級進行排序。
例1:爲一個對象指定過期時間
首先,我們先定義一個元素,這個元素要實現Delayed接口
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
public class DelayedElement implements Delayed {
//過期時間
private long expired;
private long delay;
private String name;
public DelayedElement(String elementName, long delay) {
this.name = elementName;
this.delay= delay;
this.expired = ( delay + System. currentTimeMillis());
}
@Override
public int compareTo(Delayed o) {
//因爲比較的DelayedElement類,所以必須先強制類型轉換。
DelayedElement cached=(DelayedElement) o;
//DelayedQueue中的元素需要進行排序,一般情況,我們都是按元素過期時間的優先級進行排序。
return cached.getExpired() > this.expired ? 1 : -1;
}
@Override
public long getDelay(TimeUnit unit) {
//過期時間減當前時間 (如果當前時間 到達 過期時間,該元素被釋放)
return ( expired - System. currentTimeMillis());
}
@Override
public String toString() {
return "DelayedElement [delay=" + delay + ", name=" + name + "]";
}
public long getExpired() {
return expired;
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
DelayQueue<DelayedElement> queue = new DelayQueue<>();
//設置這個元素的過期時間爲3s
DelayedElement ele = new DelayedElement("cache 3 seconds", 3000);
queue.put(ele);
System.out.println(queue.take());
}
}
運行這個main函數,我們可以發現,我們需要等待3s之後纔會打印這個對象(toString())。
DelayedElement [delay=3000, name=cache 3 seconds]
其實DelayQueue應用場景很多,比如定時關閉連接、緩存對象,超時處理等各種場景,下面我們就拿學生考試爲例讓大家更深入的理解DelayQueue的使用。
例2:把所有考試的學生看做是一個DelayQueue,誰先做完題目釋放誰
首先,我們構造一個學生對象
public class Student implements Runnable, Delayed {
private String name; // 姓名
private long costTime;// 做試題的時間
private long finishedTime;// 完成時間
public Student(String name, long costTime) {
this.name = name;
this.costTime = costTime;
finishedTime = costTime + System.currentTimeMillis();
}
@Override
public void run() {
System.out.println(name + " 交卷,用時" + costTime / 1000);
}
@Override
public long getDelay(TimeUnit unit) {
return (finishedTime - System.currentTimeMillis());
}
@Override
public int compareTo(Delayed o) {
Student other = (Student) o;
return costTime >= other.costTime ? 1 : -1;
}
}
然後在構造一個教師對象對學生進行考試
public class Teacher {
static final int STUDENT_SIZE = 30;
public static void main(String[] args) throws InterruptedException {
Random r = new Random();
// 把所有學生看做一個延遲隊列
DelayQueue<Student> students = new DelayQueue<Student>();
// 構造一個線程池用來讓學生們“做作業”
ExecutorService exec = Executors.newFixedThreadPool(STUDENT_SIZE);
for (int i = 0; i < STUDENT_SIZE; i++) {
// 初始化學生的姓名和做題時間
students.put(new Student("學生" + (i + 1), 3000 + r.nextInt(10000)));
}
// 開始做題
while (!students.isEmpty()) {
exec.execute(students.take());
}
exec.shutdown();
}
}
我們看一下運行結果:
學生4 交卷,用時3
學生13 交卷,用時4
學生5 交卷,用時5
學生14 交卷,用時5
學生2 交卷,用時5
學生3 交卷,用時6
學生26 交卷,用時6
通過運行結果我們可以發現,每個學生在指定開始時間到達之後就會“交卷”(取決於getDelay()方法),並且是先做完的先交卷(取決於compareTo()方法)。
4)PriorityBlockingQueue優先級對列
4.1 PriorityBlockingQueue是一個沒有邊界的隊列,它的排序規則和 java.util.PriorityQueue一樣。需要注意,PriorityBlockingQueue中允許插入null對象。
基於優先級的阻塞隊列(優先級的判斷通過構造函數傳入的Compator對象來決定),但需要注意的是PriorityBlockingQueue並不會阻塞數據生產者,而只會在沒有可消費的數據時,阻塞數據的消費者。因此使用的時候要特別注意,生產者生產數據的速度絕對不能快於消費者消費數據的速度,否則時間一長,會最終耗盡所有的可用堆內存空間。在實現PriorityBlockingQueue時,內部控制線程同步的鎖採用的是公平鎖。
4.2 所有插入PriorityBlockingQueue的對象必須實現 java.lang.Comparable接口,隊列優先級的排序規則就是按照我們對這個接口的實現來定義的。
規則是:當前和其他對象比較,如果compare方法返回負數,那麼在隊列裏面的優先級就比較高。
4.3 另外,我們可以從PriorityBlockingQueue獲得一個迭代器Iterator,但這個迭代器並不保證按照優先級順序進行迭代。
問題:有沒有人在使用時,發現將添加在PriorityBlockingQueue的一系列元素打印出來,隊列的元素其實並不是全部按優先級排序的,但是隊列頭的優先級肯定是最高的?
回覆:PriorityBlockingQueue隊列添加新元素時候不是將全部元素進行順序排列。但是每取一個頭元素時候,都會對剩餘的元素做一次調整,這樣就能保證每次隊列頭的元素都是優先級最高的元素
下面我們舉個例子來說明一下,首先我們定義一個對象類型,這個對象需要實現Comparable接口:
public class PriorityElement implements Comparable<PriorityElement> {
private int priority;// 定義優先級
public PriorityElement(int priority) {
// 初始化優先級
this.priority = priority;
}
@Override
public int compareTo(PriorityElement o) {
// 按照優先級大小進行排序
return priority >= o.getPriority() ? 1 : -1;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
@Override
public String toString() {
return "PriorityElement [priority=" + priority + "]";
}
}
然後我們把這些元素隨機設置優先級放入隊列中
public class PriorityBlockingQueueExample {
public static void main(String[] args) throws InterruptedException {
PriorityBlockingQueue<PriorityElement> queue = new PriorityBlockingQueue<>();
for (int i = 0; i < 5; i++) {
Random random = new Random();
PriorityElement ele = new PriorityElement(random.nextInt(10));
queue.put(ele);
}
while (!queue.isEmpty()) {
System.out.println(queue.take());
}
}
}
看一下運行結果:
PriorityElement [priority=3]
PriorityElement [priority=5]
PriorityElement [priority=7]
PriorityElement [priority=7]
PriorityElement [priority=8]
5)SynchronousQueue
特殊的BlockingQueue,對其的操作必須是放和取交替完成的. 隊列內部僅允許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。
一種無緩衝的等待隊列,類似於無中介的直接交易,有點像原始社會中的生產者和消費者,生產者拿着產品去集市銷售給產品的最終消費者,而消費者必須親自去集市找到所要商品的直接生產者,如果一方沒有找到合適的目標,那麼對不起,大家都在集市等待。相對於有緩衝的BlockingQueue來說,少了一箇中間經銷商的環節(緩衝區),如果有經銷商,生產者直接把產品批發給經銷商,而無需在意經銷商最終會將這些產品賣給那些消費者,由於經銷商可以庫存一部分商品,因此相對於直接交易模式,總體來說採用中間經銷商的模式會吞吐量高一些(可以批量買賣);但另一方面,又因爲經銷商的引入,使得產品從生產者到消費者中間增加了額外的交易環節,單個產品的及時響應性能可能會降低。
聲明一個SynchronousQueue有兩種不同的方式,它們之間有着不太一樣的行爲。公平模式和非公平模式的區別:
如果採用公平模式:SynchronousQueue會採用公平鎖,並配合一個FIFO隊列來阻塞多餘的生產者和消費者,從而體系整體的公平策略;
但如果是非公平模式(SynchronousQueue默認):SynchronousQueue採用非公平鎖,同時配合一個LIFO隊列來管理多餘的生產者和消費者,而後一種模式,如果生產者和消費者的處理速度有差距,則很容易出現飢渴的情況,即可能有某些生產者或者是消費者的數據永遠都得不到處理。
學習鏈接:
http://blog.csdn.net/suifeng3051/article/details/48807423
http://www.cnblogs.com/liuling/p/2013-8-20-01.html
http://wsmajunfeng.iteye.com/blog/1629354
http://blog.sina.com.cn/s/blog_6145ed8101010q1y.html