【Java進階】併發程式設計
PS:整理自極客時間 《Java併發程式設計》
1. 概述
- 三種性質
- 可見性 :一個執行緒對共享變數的修改,另一個執行緒能立刻看到。 快取 可導致可見性問題。
- 原子性 :一個或多個CPU執行操作不被中斷。 執行緒切換 可導致原子性問題。
- 有序性 :編譯器優化可能導致指令順序發生改變。 編譯器優化 可能導致有序性問題。
- 三個問題
- 安全性問題 :執行緒安全
- 活躍性問題 :死鎖、活鎖、飢餓
- 效能問題 :
- 使用無鎖結構 :TLS,Copy-On-Write,樂觀鎖;Java的原子類,Disruptor無鎖佇列
- 減少鎖的持有時間 :讓鎖 細粒 度。如ConcurrentHashmap;再如讀寫鎖,讀無鎖寫有鎖
2. Java記憶體模型
- volatile
- C語言中的原意 :禁用CPU快取,從記憶體中讀出和寫入。
- Java語言的引申義 :
- Java會將變數立刻寫入記憶體,其他執行緒讀取時直接從記憶體讀(普通變數改變後,什麼時候寫入記憶體是不一定的)
- 禁止指令重排序
- 解決問題 :
- 保證可見性
- 保證有序性
- 不能保證原子性
- Happens-Before規則(H-B)
- 程式順序性規則 :前面執行的語句對後面語句可見
- volatile變數規則 :volatile變數的寫操作對後續的讀操作可見
- 傳遞性規則 :A H-B B,B H-B C,那麼A H-B C
- 管程中鎖的規則 :對一個鎖的解鎖 H-B於 後續對這個鎖的加鎖
3. 互斥鎖sychronized
- 鎖物件 :非靜態this,靜態Class,括號Object引數
- 預防死鎖:
- 互斥 :不能破壞
- 佔有且等待 :同時申請所有資源
- 不可搶佔 :sychronized解決不了,Lock可以解決
- 迴圈等待 :給資源設定id欄位,每次都是按順序申請鎖
- 等待通知機制 :
- wait、notify、notifyAll
class Allocator { private List<Object> als; // 一次性申請所有資源 synchronized void apply( Object from, Object to){ // 經典寫法 while(als.contains(from) || als.contains(to)){ try{ wait(); }catch(Exception e){ } } als.add(from); als.add(to); } // 歸還資源 synchronized void free( Object from, Object to){ als.remove(from); als.remove(to); notifyAll(); } }
4. 執行緒的生命週期
- 通用執行緒的生命週期 :
-
- Java執行緒的生命週期 :
-
- 狀態流轉 :
- RUNNABLE -- BLOCKED :執行緒獲取和等待sychronized隱式鎖
- ps:呼叫阻塞式API時,不會進入BLOCKED狀態,但對於作業系統而言,執行緒實際上進入了休眠態,只不過JVM不關心。
- RUNNABLE -- WAITING :
- Object.wait()
- Thread.join()
- LockSupport.park()
- RUNNABLE -- TIMED-WAITING :呼叫各種帶超時引數的執行緒方法
- NEW -- RUNNABLE : Thread.start()
- RUNNABLE -- TERMINATED :執行緒執行完畢,有異常丟擲,或手動呼叫執行緒 stop()
- RUNNABLE -- BLOCKED :執行緒獲取和等待sychronized隱式鎖
6. 執行緒的效能指標
- 延遲 :發出請求到收到響應
- 吞吐量 :單位時間內處理的請求數量
- 最佳執行緒數:
- CPU密集型 :執行緒數 = CPU核數 + 1
- IO密集型 :執行緒數 = (IO耗時/CPU耗時 + 1)* CPU核數
-
7. JDK併發包
- Lock :lock、unlock
- 互斥鎖 ,和sychronized一樣的功能,裡面能保證可見性
- Condition :await、signal
- 條件 ,相比於sychronized的Object.wait,Condition可以實現多條件喚醒等待機制
- Semaphore :acquire、release
- 訊號量 ,可以用來實現多個執行緒訪問一個臨界區,如實現物件池設計中的限流器
- ReadWriteLock :readLock、writeLock
- 寫鎖、讀鎖 ,允許多執行緒讀,一個執行緒寫,寫鎖持有時所有讀鎖和寫鎖的獲取都阻塞(寫鎖的獲取要等所有讀寫鎖釋放)
- 適用於讀多寫少的場景
- StampedLock :tryOptimisticRead、validate
- 寫鎖、讀鎖 (分 悲觀讀鎖、樂觀讀鎖 ):
- 執行緒同步:
- CountDownLatch :一個執行緒等待多個執行緒
- 初始化 --> countDown(減1) --> await(等待為0)
- CyclicBarrier :一組執行緒之間相互等待
- 初始化 --> 設定回撥函式(為0時執行,並返回原始值) --> await(減1並等待為0)
- CountDownLatch :一個執行緒等待多個執行緒
- 併發容器:
- List:
- CopyOnWriteArrayList :適用寫少的場景,要容忍可能的讀不一致
- Map:
- ConcurrentHashMap :分段鎖
- ConcurrentSkipListMap :跳錶
- Set:
- CopyOnWriteArraySet :同上
- ConcurrentSkipListSet :同上
- Queue :
- 分類 :阻塞Blocking、單端Queue、雙端Deque
- 單端阻塞(BlockingQueue) :Array~、Linked~、Sychronized~、LinkedTransfer~、Priority~、Delay~
- 雙端阻塞(BlockingDeque) :Linked~
- 單端非阻塞(Queue) :ConcurrentLinked~
- 雙端非阻塞(Deque) :ConcurrentLinked~
- List:
- 原子類:
- 無鎖方案原理 :增加了硬體支援,即CPU的CAS指令
- ABA問題 :有解決ABA問題的需求時,增加一個遞增的版本號緯度化解
- 分類 :原子化基本資料型別,原子化引用型別、原子化陣列、原子化物件屬性更新器、原子化累加器
- Future:
- Future :cancel、isCanceled、isDone、get
- FutureTask :實現了Runnable和Future介面
- 強大工具類
- CompletableFuture :一個強大的非同步程式設計工具類(任務之間有聚合關係),暫時略
- CompletionService :批量並行任務,暫時略
8. 執行緒池
- 設計原理:
- 用 生產者消費 者模型,執行緒池是消費者,呼叫者是生產者。
- 執行緒池物件裡維護一個阻塞 佇列 ,一個已經跑起來的工作執行緒組T hreadsList
- ThreadList裡面迴圈從佇列中去Runnable任務,並呼叫run方法
-
1 // 簡化的執行緒池,僅用來說明工作原理 2 class MyThreadPool{ 3// 利用阻塞佇列實現生產者 - 消費者模式 4BlockingQueue<Runnable> workQueue; 5// 儲存內部工作執行緒 6List<WorkerThread> threads 7= new ArrayList<>(); 8// 構造方法 9MyThreadPool(int poolSize, 10BlockingQueue<Runnable> workQueue){ 11this.workQueue = workQueue; 12// 建立工作執行緒 13for(int idx=0; idx<poolSize; idx++){ 14WorkerThread work = new WorkerThread(); 15work.start(); 16threads.add(work); 17} 18} 19// 提交任務 20void execute(Runnable command){ 21workQueue.put(command); 22} 23// 工作執行緒負責消費任務,並執行任務 24class WorkerThread extends Thread{ 25public void run() { 26// 迴圈取任務並執行 27while(true){ ① 28Runnable task = workQueue.take(); 29task.run(); 30} 31} 32} 33 } 34 35 /** 下面是使用示例 **/ 36 // 建立有界阻塞佇列 37 BlockingQueue<Runnable> workQueue = 38new LinkedBlockingQueue<>(2); 39 // 建立執行緒池 40 MyThreadPool pool = new MyThreadPool( 4110, workQueue); 42 // 提交任務 43 pool.execute(()->{ 44System.out.println("hello"); 45 });
- ThreadPoolExcutor
- 引數
- corePoolSize :執行緒池保有的最小執行緒數
- maximumPoolSize :執行緒池建立的最大執行緒數
- keepAliveTime :工作執行緒多久沒收到任務,被認為是閒的
- workQueue :工作佇列
- threadFactory :通過這個引數自定義如何建立執行緒
- handler :任務拒絕策略
- 預設為 AbortPolicy ,會丟擲RejectedExecutionException,這是個執行時異常,要注意
- 方法
- void execute()
- Future submit(Runnable task | Callable task)
- 引數
9. 鳥瞰並行任務分類