《Java併發編程實戰》第五章筆記

基礎構建模塊

在這裏插入圖片描述

同步容器類

  • 同步容器類包括 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 類型,除非需要他們是可變的。
  • 不可變對象一定是線程安全的。
    • 不可變對象能極大地降低併發編程的複雜性。他們更爲簡單而且安全,可以任意共享而無須使用加鎖或保護性複製等機制。
  • 封裝有助於管理複雜性
    • 在編寫線程安全的程序時,雖然可以將所有數據都保存在全局變量中,但爲什麼要這樣做?將數據封裝在對象中,更易於維持不變性條件:將同步機制封裝在對象中,更易於遵循同步策略。
  • 用鎖來保護每個可變變量。
  • 當保護同一個不變性條件中的所欲變量時,要使用同一個鎖。
  • 在執行復合操作期間,要持有鎖。
  • 如果從多個線程中訪問同一個可變變量時沒有同步機制,那麼程序會出現問題。
  • 不要故作聰明地推斷出不需要使用同步。
  • 在設計過程中考慮線程安全,或者在文檔中明確地指出它不是線程安全的。
  • 將同步策略文檔化。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章