標籤:發布 lock mem 所有權 one 修改 發行者 結果 load
委託是建立安全執行緒類的一個最有效策略:只需讓現有的安全執行緒類管理所有的狀態即可。
一、同步容器類
1、同步容器類的問題
同步容器類都是安全執行緒的,容器本身內建的複合操作能夠保證原子性,但是當在其上進行用戶端複合操作則需要額外加鎖保護其安全性
由於同步容器類要遵守同步策略,即支援用戶端加鎖,但必須清楚加入同一個鎖
2、迭代器與ConcurrentModificationException
及時失敗機制:容器在迭代過程中被修改時 ,就會拋出一個ConcurrentModificationException異常
解決方案:加鎖或棄置站台
3、隱藏迭代器
一些隱藏的迭代操作:hashCode, equals, containsAll, removeAll, retainAll等
二、並發容器
同步容器對容器狀態訪問實現序列化以保證安全執行緒,但這種方法嚴重降低了並發性。通過並發容器來代替同步容器,可以極大地提高伸縮性並降低風險。
BlockingQueue擴充了Queue,實現了可阻塞的插入和擷取等操作ConcurrentHashMap代替HashMap
1、ConcurrentHashMap
並不是在每個方法上都在鎖使得只有一個線程可以訪問容器,即沒有實現獨佔訪問。而是使用一種粒度更細的加鎖機制來實現大程度的共用,這種機制稱為分段鎖(Lock Striping)
ConcurrentHashMap的迭代器不會拋出ConcurrentModificationException,因此不需要在迭代過程中加鎖,因為其返回的迭代器具有弱一致性,而非"及時失敗"。
ConcurrentHashMap對一些操作進行了弱化,如size(計算的是近似值,而不是精確值), isEmpty等
2、額外的原子Map操作
ConcurrentHashMap實現了若沒有則添加、若有則刪除、映射則替換等操作的介面
3、CopyOnWriteArrayList
特點:寫入時複製,即每當修改容器時都會複製底層數組產生開銷,只要發布一個事實不可變的對象,那麼在訪問該對象時就不需要進一步同步
不會拋出ConcurrentModificationException,不用加鎖,效能更好
僅當迭代操作遠遠多於修改操作時,才應該使用"寫入時複製"容器
三、阻塞隊列和生產者消費者模式
阻塞隊列提供了可阻塞的put和take方法,以及支援定時的offer和poll方法。(offer方法如果資料不能添加到隊列則返回一個失敗狀態)
可有界也可無界
在構建高可靠的應用程式時,有界隊列是一種強大的資源管理工具;它們能夠意志或防止產生過多的工作項目,使應用程式在負荷過載的情況下變得更加健壯。
實現:LinkedBlockingQueue, ArrayBlockingQueue, PriorityBlockingQueue(可實現comparable方法比較排序),SynchronousQueue(維護一組背景工作執行緒,而不是維護隊列元素的儲存空間)
1、串列線程封閉
對於可變對象,生產者--消費者這種設計與阻塞隊列一起,促進了串列線程封閉,從而將對象所有權從生產者交付給消費者。
線程封閉對象由單個線程所有,但通過生產者消費者模式安全的轉移了對象的所有權,轉移後只有接受的線程獲得該對象的所有權,而發行者放棄了所有權,並不會在訪問他。
2、雙端隊列適用於工作密取
Deque和BlockingDeque對Queue進行了拓展,實現了雙端隊列,即從頭尾皆可插入刪除。(實現:ArrayDeque,LinkedBlockingDeque)
每個消費者有各自的雙端隊列,當消費者自己的雙端隊列為空白時,它會從其他消費者隊列末尾中密取任務。優點:大大減少了競爭,保證線程均出於忙碌狀態
四、阻塞方法和中斷方法
阻塞的原因:等待I/O操作結束,等待獲得一個鎖,等待從Thread.sleep方法中醒來,或是等待另一個線程的計算結果等
線程阻塞時會被掛起,處於某種阻塞狀態(BLOCKED,WAITING,TIMED_WAITING),並且必須等待某個不受他控制的事件完成
拋出InterruptedException的方法叫做阻塞方法
中斷是一種協作機制,一個線程不能強制要求其他線程停止正在執行的操作而去執行其他動作。
處理對中斷的響應:傳遞InterreuptedException,拋出異常給方法調用者,或捕獲異常,做一些清理工作再拋出拋出異常;恢複中斷:有時不能拋出InterruptedException, 比如在Runnable中,則可以恢複中斷
五、同步工具類
1、閉鎖:確保某些活動直到其他活動都完成後才繼續執行
閉鎖的作用相當於一扇門:在閉鎖到達結束狀態之前,這扇門一直是關閉的,並且沒有任何線程能通過,當到達結束狀態時,這扇門會開啟並允許所有的線程通過。當閉鎖到達結束狀態後,將不會再改變狀態。
CountDownLatch:一個或多個線程等待一組事件發生。閉鎖狀態包括一個計數器,該計數器被初始化為一個正數,表示需要等待的事件數目量。countDown方法遞減計數器,表示有一個事件已經發生了,而await方法阻塞直到計數器到達零
1 public long timeTasks(int nThreads, final Runnable task) throws InterruptedException{ 2 final CountDownLatch startGate = new CountDownLatch(1); //所有線程同時開始執行task的閥門 3 final CountDownLatch endGate = new CountDownLatch(nThreads); //所有線程結束的閥門 4 5 for (int i=0; i<nThreads; i++){ 6 Thread t = new Thread(){ 7 @Override 8 public void run() { 9 try {10 startGate.await(); //等待startGate值減為011 try {12 task.run();13 } finally{14 endGate.countDown(); //一個線程運行結束,值減115 }16 } catch (InterruptedException e) {17 e.printStackTrace();18 }19 }20 };21 t.start();22 }23 long start = System.nanoTime();24 startGate.countDown(); //所有線程開始執行task25 endGate.await(); //等待所有線程執行結束26 long end = System.nanoTime();27 return end - start;28 }
2、FutureTask
FutureTask是通過 Callable 來實現的,相當於一種可產生結果的 Runnable,並且可處於以下三種狀態:等待運行,正在運行,運行完成(正常完成、取消、異常結束)。當FutureTask進入完成狀態後,它會停留在這個狀態上。
Future.get 用來擷取計算結果,如果FutureTask還未運行完成,則會阻塞。FutureTask 將計算結果從執行計算的線程傳遞到擷取這個結果的線程,而FutureTask 的規範確保了這種傳遞過程能實現結果的安全發布。
1 import java.util.concurrent.Callable; 2 import java.util.concurrent.ExecutionException; 3 import java.util.concurrent.FutureTask; 4 5 public class Preloader { 6 private final FutureTask<Integer> future = new FutureTask<>(new Callable() { 7 public Integer call() throws Exception { 8 return 969*99*99; 9 }10 });11 12 private final Thread thread = new Thread(future);13 14 public void start() {15 thread.start();16 }17 18 public Integer get() throws Exception {19 try {20 return (Integer) future.get();21 } catch (ExecutionException e) {22 Throwable cause = e.getCause();23 throw launderThrowable(cause);24 }25 }26 27 private static Exception launderThrowable(Throwable cause) {28 if (cause instanceof RuntimeException)29 return (RuntimeException) cause;30 else if (cause instanceof Error)31 throw (Error) cause;32 else33 throw new IllegalStateException("Not Checked", cause);34 }35 36 public static void main(String[] args) throws Exception {37 Preloader p = new Preloader();38 p.start();39 long start = System.currentTimeMillis();40 System.out.println(p.get());41 System.out.println(System.currentTimeMillis() - start);42 }43 }
3、訊號量
計數訊號量用來控制同時訪問某個特定資源的運算元量,或同時執行某個指定操作的數量。或者可以用來實現某種資源集區,或者對容器施加邊界。
Semaphore管理一組虛擬許可,有建構函式指定數量(數量為1即為互斥鎖),acquire請求許可(阻塞直到獲得,或中斷,或逾時),release釋放一個許可
使用Semaphore實現有界阻塞容器
1 public class BoundedList<T> { 2 3 private final List<T> list; 4 private final Semaphore semaphore; 5 6 public BoundedList(int bound) { 7 list = Collections.synchronizedList(new LinkedList<T>()); 8 semaphore = new Semaphore(bound); 9 } 10 11 public boolean add(T obj) throws InterruptedException { 12 semaphore.acquire(); 13 boolean addedFlag = false; 14 try { 15 addedFlag = list.add(obj); 16 } 17 finally { 18 if (!addedFlag) { 19 semaphore.release(); 20 } 21 } 22 return addedFlag; 23 } 24 25 public boolean remove(Object obj) { 26 boolean removedFlag = list.remove(obj); 27 if (removedFlag) { 28 semaphore.release(); 29 } 30 return removedFlag; 31 }32 }
4、柵欄
柵欄類似於閉鎖,它能阻塞一組線程直到某個事件發生
閉鎖用於等待事件,而柵欄用於等待其他線程。閉鎖是一次性對象,一旦進入終止狀態,就不能被重設
六、構建高效可伸縮的結果緩衝
首先考慮採用HashMap,通過sychronized方法滿足原子性
–> 效能較差,同一時間只有一個線程進行計算操作,使用ConcurrentHashMap改善效能,無需使用同步方法,但是可能導致很多線程在計算同樣的值
–> 考慮阻塞方法,使用基於FutureTask的ConcurrentHashMap,Future.get實現阻塞知道結果返回,減少了多次計算,但仍然不是原子性的
–> 使用ConcurrentHashMap中的 putifAbsent()
–> 繼續解決緩衝汙染問題,當緩衝結果失效時移除,解決緩衝逾期,緩衝清理等等問題
1 public class Memoizer <A, V> implements Computable<A, V> { 2 private final ConcurrentMap<A, Future<V>> cache 3 = new ConcurrentHashMap<A, Future<V>>(); 4 private final Computable<A, V> c; 5 6 public Memoizer(Computable<A, V> c) { 7 this.c = c; 8 } 9 10 public V compute(final A arg) throws InterruptedException {11 while (true) {12 Future<V> f = cache.get(arg);13 if (f == null) {14 Callable<V> eval = new Callable<V>() {15 public V call() throws InterruptedException {16 return c.compute(arg);17 }18 };19 FutureTask<V> ft = new FutureTask<V>(eval);20 f = cache.putIfAbsent(arg, ft);21 if (f == null) {22 f = ft;23 ft.run();24 }25 }26 try {27 return f.get();28 } catch (CancellationException e) {29 cache.remove(arg, f);30 } catch (ExecutionException e) {31 throw LaunderThrowable.launderThrowable(e.getCause());32 }33 }34 }35 }
java並發編程實戰:第五章----基礎構建模組