BlockingQueue
BlockingQueue
接口表示它是一個Queue
,意思是它的項以先入先出(FIFO)順序存儲。在特定順序插入的項以相同的順序檢索,但是需要附加保證,從空隊列檢索一個項的任何嘗試都會阻塞調用線程,直到這個項準備好被檢索。同理,想要將一個項插入到滿隊列的嘗試也會導致阻塞調用線程,直到隊列的存儲空間可用。BlockingQueue
乾淨利落地解決了如何將一個線程收集的項“傳遞”給另一線程用於處理的問題,無需考慮同步問題。
ArrayBlockingQueue
:一個由數組支持的有界隊列。LinkedBlockingQueue
:一個由鏈接節點支持的可選有界隊列。PriorityBlockingQueue
:一個由優先級堆支持的無界優先級隊列。DelayQueue
:一個由優先級堆支持的、基於時間的調度隊列。SynchronousQueue
:一個利用BlockingQueue
接口的簡單聚集(rendezvous)機制。
前兩個類 ArrayBlockingQueue
和 LinkedBlockingQueue
幾乎相同,只是在後備存儲器方面有所不同,
LinkedBlockingQueue
並不總是有容量界限。無大小界限的LinkedBlockingQueue
類在添加元素時永遠不會有阻塞隊列的等待(至少在其中有Integer.MAX_VALUE
元素之前不會)。
PriorityBlockingQueue
是具有無界限容量的隊列,它利用所包含元素的 Comparable
排序順序來以邏輯順序維護元素。可以將它看作TreeSet
的可能替代物。例如,在隊列中加入字符串 One、Two、Three 和 Four 會導致 Four 被第一個取出來。對於沒有天然順序的元素,可以爲構造函數提供一個Comparator
。不過對
PriorityBlockingQueue
有一個技巧。從 iterator()
返回的 terator實例不需要以優先級順序返回元素。如果必須以優先級順序遍歷所有元素,那麼讓它們都通過toArray()
方法並自己對它們排序,像Arrays.sort(pq.toArray())
。
DelayQueue
實現可能是其中最有意思(也是最複雜)的一個。加入到隊列中的元素必須實現新的 Delayed
接口。因爲隊列的大小沒有界限,使得添加可以立即返回,但是在延遲時間過去之前,不能從隊列中取出元素。如果多個元素完成了延遲,那麼最早失效/失效時間最長的元素將第一個取出。實際上沒有聽上去這樣複雜。
之前實現的生產者消費者就可以實現得更優雅了,效率應該也會更高的吧(沒做性能測試,猜測)。
代碼如下:MyBlockingQueue.Java
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
class Producer implements Runnable {
private BlockingQueue drop;
List messages = Arrays.asList("Mares eat oats", "Does eat oats", "Little lambs eat ivy", "Wouldn't you eat ivy too?");
public Producer(BlockingQueue d) {
this.drop = d;
}
public void run() {
try {
for (String s : messages)
drop.put(s);
drop.put("DONE");
} catch (InterruptedException intEx) {
System.out.println("Interrupted! " + "Last one out, turn out the lights!");
}
}
}
class Consumer implements Runnable {
private BlockingQueue drop;
public Consumer(BlockingQueue d) {
this.drop = d;
}
public void run() {
try {
String msg = null;
while (!((msg = drop.take()).equals("DONE")))
System.out.println(Thread.currentThread().getName() + " cost: " + msg);
} catch (InterruptedException intEx) {
System.out.println("Interrupted! " + "Last one out, turn out the lights!");
}
}
}
public class MyBlockingQueue {
public static void main(String[] args) {
BlockingQueue drop = new ArrayBlockingQueue(1, true);
(new Thread(new Producer(drop), "Producer")).start();
(new Thread(new Consumer(drop), "Consumer1")).start();
(new Thread(new Consumer(drop), "Consumer2")).start();
}
}
DelayQueue
的示例代碼,MyDelay.java
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
class NanoDelay implements Delayed {
long trigger;
NanoDelay(long i) {
trigger = System.nanoTime() + i;
}
public boolean equals(Object other) {
return ((NanoDelay) other).trigger == trigger;
}
public boolean equals(NanoDelay other) {
return ((NanoDelay) other).trigger == trigger;
}
public long getTriggerTime() {
return trigger;
}
public String toString() {
return String.valueOf(trigger);
}
@Override
public int compareTo(Delayed o) {
long i = trigger;
long j = ((NanoDelay) o).trigger;
if (i < j) return -1; if (i > j)
return 1;
return 0;
}
@Override
public long getDelay(TimeUnit unit) {
long n = trigger - System.nanoTime();
return unit.convert(n, TimeUnit.NANOSECONDS);
}
}
public class MyDelay {
public static void main(String args[]) throws InterruptedException {
Random random = new Random();
BlockingQueue queue = new DelayQueue();
for (int i = 0; i < 5; i++) {
queue.add(new NanoDelay(random.nextInt(1000)));
}
long last = 0;
for (int i = 0; i < 5; i++) {
NanoDelay delay = queue.take();
long tt = delay.getTriggerTime();
System.out.println("Trigger time: " + tt);
if (i != 0) {
System.out.println("Delta: " + (tt - last));
}
last = tt;
}
}
}
SynchronousQueue
的示例代碼,MySynchronousQueues.java
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
class Producer implements Runnable {
private BlockingQueue drop;
List messages = Arrays.asList("Mares eat oats", "Does eat oats", "Little lambs eat ivy", "Wouldn't you eat ivy too?");
public Producer(BlockingQueue d) {
this.drop = d;
}
public void run() {
try {
for (String s : messages)
drop.put(s);
drop.put("DONE");
} catch (InterruptedException intEx) {
System.out.println("Interrupted! " + "Last one out, turn out the lights!");
}
}
}
class Consumer implements Runnable {
private BlockingQueue drop;
public Consumer(BlockingQueue d) {
this.drop = d;
}
public void run() {
try {
String msg = null;
while (!((msg = drop.take()).equals("DONE")))
System.out.println(Thread.currentThread().getName() + " cost: " + msg);
} catch (InterruptedException intEx) {
System.out.println("Interrupted! " + "Last one out, turn out the lights!");
}
}
}
public class MySynchronousQueues {
public static void main(String[] args) {
BlockingQueue drop = new SynchronousQueue();
(new Thread(new Producer(drop), "Producer")).start();
(new Thread(new Consumer(drop), "Consumer1")).start();
(new Thread(new Consumer(drop), "Consumer2")).start();
}
}
ConcurrentMap
Map
有一個微妙的併發 bug,ConcurrentMap
是最容易的解決方案。當一個Map
被從多個線程訪問時,通常使用containsKey()
或者get()
來查看給定鍵是否在存儲鍵/值對之前出現。但是即使有一個同步的Map
,線程還是可以在這個過程中潛入,然後奪取對
Map
的控制權。問題是,在對put()
的調用中,鎖在 get()
開始時獲取,然後在可以再次獲取鎖之前釋放。它的結果是個競爭條件:這是兩個線程之間的競爭,結果也會因誰先運行而不同。如果兩個線程幾乎同時調用一個方法,兩者都會進行測試,調用 put,在處理中丟失第一線程的值。ConcurrentHashMap
實現只能在鍵不存在時將元素加入到map中,只有在鍵存在並映射到特定值時才能從map中刪除一個元素。有一個新的putIfAbsent()
方法用於在 map 中進行添加。這個方法以要添加到 ConcurrentMap
實現中的鍵的值爲參數,就像普通的 put()
方法,但是只有在map不包含這個鍵時,才能將鍵加入到map中。如果map已經包含這個鍵,那麼這個鍵的現有值就會保留。
putIfAbsent()
方法是原子的。
CopyOnWriteArrayList/CopyOnWriteArraySet
這是ArrayList
的一個線程安全的變體,其中所有可變操作(add、set 等等)都是通過對底層數組進行一次新的複製來實現的。這一般需要很大的開銷,但是當遍歷操作的數量大大超過可變操作的數量時,這種方法可能比其他替代方法更有效。在不能或不想進行同步遍歷,但又需要從併發線程中排除衝突時,它也很有用。“快照”風格的迭代器方法在創建迭代器時使用了對數組狀態的引用。此數組在迭代器的生存期內不會更改,因此不可能發生衝突,並且迭代器保證不會拋出ConcurrentModificationException
。創建迭代器以後,迭代器就不會反映列表的添加、移除或者更改。在迭代器上進行的元素更改操作(remove、set
和 add)不受支持。這些方法將拋出UnsupportedOperationException
。
有一點需要注意的是,調用add()
方法都要引起數組拷貝,所以儘量使用addAll()
,避免在循環中使用add()
方法。