文章目錄
基礎構建模塊
同步容器類
- 同步容器類包括 Vector 和 Hashtable,二者是早期 JDK 的一部分,此外還包括在JDK 1.2中添加的一些功能相似的類,這些同步的封裝器類是由 Collections.synchronizedXxx 等工廠方法創建的。這些類實現線程安全的方式是:將他們的狀態封裝起來,並對每個公有方法都進行同步,使得每次只有一個線程能訪問容器的狀態。
同步容器類的問題
// Vector 上可能導致混亂結果的符合操作
public static Object getLast (Vector list) {
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
public static void deleteLast (Vector list) {
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
// 在使用客戶端加鎖的 Vector 上的符合操作
public static Object getLast (Vector list) {
synchronized (list) {
int lastIndex = list.size() - 1;
return list.get(lastIndex);
}
}
public static void deleteLast (Vector list) {
synchronized (list) {
int lastIndex = list.size() - 1;
list.remove(lastIndex);
}
}
- 在調用 size 和相應的 get 之間,Vector 的長度可能會發生變化,這種風險在對 Vector 中的元素進行迭代時仍然會出現。
// 可能拋出ArrayIndexOutOfBoundsException的迭代操作
for (int i = 0; i < vector.size(); i++) {
doSomething(vector.get(i));
}
// 帶有客戶端加鎖的迭代
synchronized (vector) {
for (int i = 0; i < vector.size(); i++) {
doSomething(vector.get(i));
}
}
迭代器與ConcurrentModificationException
// 通過 Iterator 來迭代 List
List<Widget> widgetList
= Collections.synchronizedList(new ArrayList<Widget>());
...
// 可能拋出 ConcurrentModificationException
for (widget w : widgetList) {
doSomething(w);
}
隱藏迭代器
// 隱藏在字符串連接中的迭代操作
public class HiddenIterator {
@GuardedBy("this")
private final Set<Integer> set = new HashSet<Integer>();
public synchronized void add (Integer i) { set.add(i); }
public synchronized void remove (Integer i) { set.remove(i); }
public void addTenThings() {
Random r = new Random();
for (int i = 0; i < 10; i++) {
add(r.nextInt());
}
System.out.println("DEBUG:added ten elements to " + set);
}
}
- 正如封裝對象的狀態有助於維持不變性條件一樣,封裝對象的同步機制同樣有助於確保實施同步策略。
併發容器
- Java 5.0 提供了多種併發容器類來改進同步容器的性能。同步容器將所有對容器狀態的訪問都串行化,以實現他們的線程安全性。這種方法的代價是嚴重降低併發性,當多個線程競爭容器的鎖時,吞吐量將嚴重減低。
- 通過併發容器來代替同步容器,可以極大地提高伸縮性並降低風險。
ConcurrentHashMap
額外的原子Map操作
// ConcurrentMap接口
public interface ConcurrentMap<K, V> extends Map<K, V> {
// 僅當K沒有相應的映射值才插入
V putIfAbsent (K key, V value);
// 僅當K被映射到V時才移除
boolean remove (K key, V value);
// 僅當K被映射到oldValue時才替換爲newValue
boolean replace (K key, V oldValue, V newValue);
// 僅當K被映射到某個值時才替換爲 newValue
V replace (K key, V newValue);
}
CopyOnWriteArrayList
阻塞隊列和生產者 - 消費者模式
- 在構建高可靠的應用程序時,有界隊列是一種強大的資源管理工具:他們能抑制並防止產生過多的工作項,使應用程序在負荷過載的情況下變得更加健壯。
示例:桌面搜索
public class FileCrawler implements Runnable {
private final BlockingQueue<File> fileQueue;
private final FileFilter fileFilter;
private final root;
public void run () {
try {
crawl(root);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void crawl (File root) throws InterruptedException {
File[] entries = root.listFiles(fileFilter);
if (entries != null) {
for (File entry : entries) {
if (entry.isDirectory()) {
crawl(entry);
} else if (!alreadyIndexed(entry)) {
fileQueue.put(entry);
}
}
}
}
}
public class Indexer implements Runnable {
private final BlockingQueue<File> queue;
public Indexer (BlockingQueue<File> queue) {
this.queue = queue;
}
public void run () {
try {
while (true) {
indexFile(queue.take());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
// 啓動桌面搜索
public static void startIndexing (File[] roots) {
BlockingQueue<File> queue = new LinkedBlockingQueue<File>(Bound);
FileFilter filter = new FileFilter() {
public boolean accept (File file) { return true; }
}
for (File root : roots) {
new Thread(new FileCrawler(queue, filter, root)).start();
}
for (int i = 0; i < N_CONSUMERS; i++) {
new Thread(new Indexer(queue)).start();
}
}
串行線程封閉
雙端隊列與工作密取
阻塞方法與中斷方法
// 恢復中斷狀態以避免屏蔽中斷
public class TaskRunnable implements Runnable {
BlockingQueue<Task> queue;
...
public void run () {
try {
processTask(queue.take());
} catch (InterruptedException e) {
// 恢復被中斷的狀態
Thread.currentThread().interrupt();
}
}
}
同步工具類
- 在容器類中,阻塞隊列是一種獨特的類:他們不僅能作爲保存對象的容器,還能協調生產者和消費者等線程之間的控制流,因爲take和put等方法將阻塞,直到隊列達到期望的狀態(隊列既非空,也非滿)。
同步工具類可以是任何一個對象,只要它根據其自身的狀態來協調線程的控制流。阻塞隊列可以作爲同步工具類,其他類型的同步工具類還包括信號量(Semaphore)、柵欄(Barrier)以及閉鎖(Latch)。
閉鎖
- 閉鎖是一種同步工具類,可以延遲線程的進度直到其到達終止狀態。
// 在計時測試中使用 CountDownLatch 來啓動和停止線程
public class TestHarness {
public long timeTasks (int nThreads, final Runnable task)
throws InterruptedException {
final CountDownLatch startGate = new CountDownLatch(1);
final CountDownLatch endGate = new CountDownLatch(nThreads);
for (int i = 0; i < nThreads; i++) {
Thread t = new Thread() {
public void run() {
try {
startGate.await();
try {
task.run();
} finally {
endGate.countDown();
}
} catch (InterruptedException ignored) { }
}
};
t.start();
}
long start = System.nanoTime();
startGate.countDown();
endGate.await();
long end = System.nanoTime();
return end - start;
}
}
FutureTask
// 使用 FutureTask 來提前加載稍後需要的數據
public class Preloader {
private final FutureTask<ProductInfo> future =
new FutureTask<ProductInfo>(new Callable<ProductInfo>() {
public ProductInfo call() throws DataLoadException {
return loadProductInfo();
}
});
private final Thread thread = new Thread(future);
public void start () { thread.start(); }
public ProductInfo get ()
throws DataLoadException, InterruptedException {
try {
return future.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof DataLoadException) {
throw (DataLoadException) cause;
} else {
throw launderThrowable(cause);
}
}
}
}
信號量
// 使用 Semaphore 爲容器設置邊界
public class BoundedHashSet<T> {
private final Set<T> set;
private final Semaphore sem;
public BoundedHashSet (int bound) {
this.set = Collections.synchronizedSet(new HashSet<T>());
sem = new Semaphore(bound);
}
public boolean add (T o) throws InterruptedException {
sem.acquire();
boolean wasAdded = false;
try {
wasAdded = set.add(o);
return wasAdded;
}
finally {
if (!wasAdded) {
sem.release();
}
}
}
public boolean remove (Object o) {
boolean wasRemoved = set.remove(o);
if (wasRemoved) {
sem.release();
}
return wasRemoved;
}
}
柵欄
// 通過 CyclicBarrier 協調細胞自動衍生系統中的計算
public class CellularAutomata {
private final Board mainBoard;
private final CyclicBarrier barrier;
private final Worker[] workers;
public CellularAutomata (Board board) {
this.mainBoard = board;
int count = Runtiome.getRuntime().availableProcessors();
this.barrier = new CyclicBarrier(count,
new Runnable() {
public void run () {
mainBoard.commitNewValues();
}
});
this.workers = new Worker[count];
for (int i = 0; i < count; i++) {
workers[i] = new Worker(mainBoard.getSubBoard(count, i));
}
}
private class Worker implements Runnable {
private final Board board;
public Worker (Board board) { this.board = board; }
public void run () {
while (!board.hasConverged()) {
for (int x = 0; x < board.getMaxX(); x++) {
for (int y = 0; y < board.getMaxY(); y++) {
board.setNewValue(x, y, computeValue(x, y));
}
try {
barrier.await();
} catch (InterruptedException ex) {
return;
} catch (BrokenBarrierException ex) {
return;
}
}
}
}
}
public void start () {
for (int i = 0; i < workers.length; i++) {
new Thread(workers[i]).start();
}
mainBoard.waitForConvergence();
}
}
構建高效且可伸縮的結果緩存
// 使用HashMap 和 同步機制來初始化緩存
public interface Computable<A, V> {
V compute (A arg) throws InterruptedException;
}
public class ExpensiveFunction
implements Computable<String, BigInteger> {
public BigInteger compute (String arg) {
// 在經過長時間的計算後
return new BigInteger(arg);
}
}
public class Memoizerl<A, V> implements Computable<A, V> {
@GuardedBy("this")
private final Map<A, V> cache = new HashMap<A, V>();
private final Computable<A, V> c;
public Memoizerl (Computable<A, V> c) {
this.c = c;
}
public synchronized V compute (A arg) throws InterruptedException {
V result = cache.get(arg);
if (result == null) {
result = c.compute(arg);
cache.put(arg, result);
}
return result;
}
}
// 用 ConcurrentHashMap 替換 HashMap
public class Memoizer2<A, V> implements Computable<A, V> {
private final Map<A, V> cache = new ConcurrentHashMap<A, V>();
private final Computable<A, V> c;
public Memoizer2 (Computable<A, V> c) { this.c = c; }
public V compute (A arg) throws InterruptedException {
V result = cache.get(arg);
if (result == null) {
result = c.compute(arg);
cache.put(arg, result);
}
return result;
}
}
// 基於 FutureTask 的 Memoizing 封裝器
public class Memoizer3<A, V> implements Computable<A, V> {
private final Map<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>>();
private final Computable<A, V> c;
public Memoizer3 (Computable<A, V> c) { this.c = c; }
public V compute (final A arg) throws InterruptedException {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V> {
public V call () throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = ft;
cache.put(arg, ft);
ft.run(); // 在這裏將調用 c.compute
}
try {
return f.get();
} catch (ExecutionException e) {
throw launderThrwable(e.getCause());
}
}
}
// Memoizer 的最終實現
public class Memoizer<A, V> implements Computable<A, V> {
private final ConcurrentMap<A, Future<V>> cache
= new ConcurrentHashMap<A, Future<V>> ();
private final Computable<A, V> c;
public Memoizer (Computable<A, V> c) { this.c = c; }
public V compute (final A arg) throws InterruptedException {
while (true) {
Future<V> f = cache.get(arg);
if (f == null) {
Callable<V> eval = new Callable<V>() {
public V call() throws InterruptedException {
return c.compute(arg);
}
};
FutureTask<V> ft = new FutureTask<V>(eval);
f = cache.putIfAbsent(arg, ft);
if (f == null) { f = ft; ft.run(); }
}
try {
return f.get();
} catch (CancellationException e) {
cache.remove(arg, f);
} catch (ExecutionException e) {
throw launderThrowable(e.getCause());
}
}
}
}
- 可變狀態是至關重要的。
- 所有的併發問題都可以歸結爲如何協調對併發狀態的訪問。可變狀態越少,就越容易確保線程安全性。
- 儘量將域聲明爲 final 類型,除非需要他們是可變的。
- 不可變對象一定是線程安全的。
- 不可變對象能極大地降低併發編程的複雜性。他們更爲簡單而且安全,可以任意共享而無須使用加鎖或保護性複製等機制。
- 封裝有助於管理複雜性
- 在編寫線程安全的程序時,雖然可以將所有數據都保存在全局變量中,但爲什麼要這樣做?將數據封裝在對象中,更易於維持不變性條件:將同步機制封裝在對象中,更易於遵循同步策略。
- 用鎖來保護每個可變變量。
- 當保護同一個不變性條件中的所欲變量時,要使用同一個鎖。
- 在執行復合操作期間,要持有鎖。
- 如果從多個線程中訪問同一個可變變量時沒有同步機制,那麼程序會出現問題。
- 不要故作聰明地推斷出不需要使用同步。
- 在設計過程中考慮線程安全,或者在文檔中明確地指出它不是線程安全的。
- 將同步策略文檔化。