Concurrent Collections 是 Java™ 5 的巨大附加產品,但是在關於註釋和泛型的爭執中很多 Java 開發人員忽視了它們。此外(或者更老實地說),許多開發人員避免使用這個數據包,因爲他們認爲它一定很複雜,就像它所要解決的問題一樣。
事實上,java.util.concurrent
包含許多類,能夠有效解決普通的併發問題,無需複雜工序。閱讀本文,瞭解 java.util.concurrent
類,比如 CopyOnWriteArrayList
和BlockingQueue
如何幫助您解決多線程編程的棘手問題。
儘管本質上 不是 Collections 類,但 java.util.concurrent.TimeUnit
枚舉讓代碼更易讀懂。使用 TimeUnit
將使用您的方法或 API 的開發人員從毫秒的 “暴政” 中解放出來。
TimeUnit
包括所有時間單位,從 MILLISECONDS
和 MICROSECONDS
到 DAYS
和 HOURS
,這就意味着它能夠處理一個開發人員所需的幾乎所有的時間範圍類型。同時,因爲在列舉上聲明瞭轉換方法,在時間加快時,將 HOURS
轉換回 MILLISECONDS
甚至變得更容易。
API http://www.gznc.edu.cn/yxsz/jjglxy/book/Java_api/java/util/concurrent/class-use/TimeUnit.html
TimeUnit tu = TimeUnit.DAYS;
System.out.println(tu.toDays(1));
System.out.println(tu.toHours(1));
System.out.println(tu.toMinutes(1));
創建數組的全新副本是過於昂貴的操作,無論是從時間上,還是從內存開銷上,因此在通常使用中很少考慮;開發人員往往求助於使用同步的 ArrayList
。然而,這也是一個成本較高的選擇,因爲每當您跨集合內容進行迭代時,您就不得不同步所有操作,包括讀和寫,以此保證一致性。
這又讓成本結構回到這樣一個場景:許多讀者都在讀取 ArrayList
,但是幾乎沒人會去修改它。
CopyOnWriteArrayList
能解決這一問題。它的 Javadoc 將 CopyOnWriteArrayList
定義爲一個 “ArrayList
的線程安全變體,在這個變體中所有易變操作(添加,設置等)可以通過複製全新的數組來實現”。
集合從內部將它的內容複製到一個沒有修改的新數組,這樣讀者訪問數組內容時就不會產生同步成本(因爲他們從來不是在易變數據上操作)。
本質上講,CopyOnWriteArrayList
很適合處理 ArrayList
經常讓我們失敗的這種場景:讀取頻繁,但很少有寫操作的集合,例如 JavaBean 事件的 Listener
s。
BlockingQueue
接口表示它是一個 Queue
,意思是它的項以先入先出(FIFO)順序存儲。在特定順序插入的項以相同的順序檢索 — 但是需要附加保證,從空隊列檢索一個項的任何嘗試都會阻塞調用線程,直到這個項準備好被檢索。同理,想要將一個項插入到滿隊列的嘗試也會導致阻塞調用線程,直到隊列的存儲空間可用。
BlockingQueue
乾淨利落地解決了如何將一個線程收集的項“傳遞”給另一線程用於處理的問題,無需考慮同步問題。Java Tutorial 的 Guarded Blocks 試用版就是一個很好的例子。它構建一個單插槽綁定的緩存,當新的項可用,而且插槽也準備好接受新的項時,使用手動同步和 wait()
/notifyAll()
在線程之間發信。(詳見 Guarded Blocks 實現。)
儘管 Guarded Blocks 教程中的代碼有效,但是它耗時久,混亂,而且也並非完全直觀。退回到 Java 平臺較早的時候,沒錯,Java 開發人員不得不糾纏於這種代碼;但現在是 2010 年 — 情況難道沒有改善?
清單 1 顯示了 Guarded Blocks 代碼的重寫版,其中我使用了一個 ArrayBlockingQueue
,而不是手寫的 Drop
。
import java.util.*;
import java.util.concurrent.*;
class Producer
implements Runnable
{
private BlockingQueue<String> drop;
List<String> messages = Arrays.asList(
"Mares eat oats",
"Does eat oats",
"Little lambs eat ivy",
"Wouldn't you eat ivy too?");
public Producer(BlockingQueue<String> 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<String> drop;
public Consumer(BlockingQueue<String> d) { this.drop = d; }
public void run()
{
try
{
String msg = null;
while (!((msg = drop.take()).equals("DONE")))
System.out.println(msg);
}
catch (InterruptedException intEx)
{
System.out.println("Interrupted! " +
"Last one out, turn out the lights!");
}
}
}
public class ABQApp
{
public static void main(String[] args)
{
BlockingQueue<String> drop = new ArrayBlockingQueue(1, true);
(new Thread(new Producer(drop))).start();
(new Thread(new Consumer(drop))).start();
}
}
ArrayBlockingQueue
還體現了“公平” — 意思是它爲讀取器和編寫器提供線程先入先出訪問。這種替代方法是一個更有效,但又冒窮盡部分線程風險的政策。(即,允許一些讀取器在其他讀取器鎖定時運行效率更高,但是您可能會有讀取器線程的流持續不斷的風險,導致編寫器無法進行工作。)