JDK并发
目錄
并發
1. 線程的生命周期
2. 線程間通信
3. 線程池
4. 內存模型
4.1 JMM抽象內存模型
4.2 happens-before規則
5. 保證并發安全的三大特性
5.1 原子性
5.2 可見性
5.3 有序性
6. JDK保證并發安全
6.1?synchronized
6.2 Lock
6.3 CAS
6.4 volatile
7. JDK并發工具
7.1?CyclicBarrier
7.2?CountDownLatch
8. 并發安全的集合
8.1 List
8.2 Map
8.3 Set
8.4 Queue
9. 原子工具類
并發
1. 線程的生命周期
1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。JDK提供了Thread(繼承)、Runnable(無返回值實現)、Callable-Future(有返回值實現)來創建線程對象。
2. 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱為“運行”。
線程對象創建后,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位于可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處于就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片后變為運行中狀態(running)。(需注意需調用start方法,線程才會進入就緒態,等待CPU執行子線程的run方法,若直接調用run方法,則是由主線程執行run方法)
3. 阻塞(BLOCKED):表示線程阻塞于鎖。
4. 等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。
5. 超時等待(TIMED_WAITING):該狀態不同于WAITING,它可以在指定的時間后自行返回。
6. 終止(TERMINATED):表示該線程已經執行完畢。
7. 守護線程
2. 線程間通信
//TODO: git上傳代碼
共享內存
wait/notify機制
Condition
管道通信PipedInputStream /PipedOutputStream
3. 線程池
Java提供了ThreadPoolExcutor線程池類使用,相關參數及邏輯如下:
- LinkedBlockingQueue 無限大小,易導致內存過大
- SynchronousQueue 生產消費隊列
- ArrayBlockingQueue 有限大小
- 拋異常(默認策略)
- 沉默處理
- 當前線程阻塞處理
- 丟棄最早的任務后重式
4. 內存模型
在講JMM前,先了解下現代計算機的內存模型。
在組成原理中,我們知道CPU是對內存進行存取,但在現代計算機中,cpu的指令速度遠超內存的存取速度,所以現代計算機系統都不得不加入一層讀寫速度盡可能接近處理器運算速度的高速緩存(Cache)來作為內存與處理器之間的緩沖:將運算需要使用到的數據復制到緩存中,讓運算能快速進行,當運算結束后再從緩存同步回內存之中,這樣處理器就無須等待緩慢的內存讀寫了。
但在多核CPU中,這引發了緩存一致性(Cache Coherence)的問題,每個處理器都有自己的高速緩存,而它們又共享同一主內存(MainMemory)。當多個處理器的運算任務都涉及同一塊主內存區域時,將可能導致各自的緩存數據不一致。為了解決一致性的問題,需要各個處理器訪問緩存時都遵循一些協議,在讀寫時要根據協議來進行操作,這類協議有MSI、MESI(Illinois Protocol)、MOSI、Synapse、Firefly及Dragon Protocol等。
4.1 JMM抽象內存模型
JMM抽象定義了主內存與工作內存
線程之間的共享變量存儲在主內存(Main Memory)中,每個線程都有一個私有的本地內存(Local Memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,并不真實存在。它涵蓋了緩存、寫緩沖區、寄存器以及其他的硬件和編譯器優化
線程A與線程B如果對同一變量進行操作的話,就會引發可見性問題:
4.2 happens-before規則
5. 保證并發安全的三大特性
5.1 原子性
原子性是指一個操作是不可中斷的,要么全部執行成功要么全部執行失敗,有著“同生共死”的感覺
例如i=i+1不是原子操作,此時并發調用該指令時,i值不會如我們預想那樣進行賦值,這便是原子性問題
5.2 可見性
可見性是指當一個線程修改了共享變量后,其他線程能夠立即得知這個修改
可參考4.1?JMM抽象內存模型,主存與工作內存不一致,便會引起可見性問題
5.3 有序性
為了性能優化,編譯器和處理器會進行指令重排序;也就是說java程序天然的有序性可以總結為:如果在本線程內觀察,所有的操作都是有序的;如果在一個線程觀察另一個線程,所有的操作都是無序的。
6. JDK保證并發安全
6.1?synchronized
保證原子性、可見性、有序性
synchronized通過對象的對象頭中的MarkWord實現,其鎖升級過程步驟如下
1.初期鎖對象剛創建時,還沒有任何線程來競爭,對象的Mark Word是下圖的第一種情形,這偏向鎖標識位是0,鎖狀態01,說明該對象處于無鎖狀態(無線程競爭它)。
2.當有一個線程來競爭鎖時,先用偏向鎖,表示鎖對象偏愛這個線程,這個線程要執行這個鎖關聯的任何代碼,不需要再做任何檢查和切換,這種競爭不激烈的情況下,效率非常高。這時Mark Word會記錄自己偏愛的線程的ID,把該線程當做自己的熟人。如下圖第二種情形。
3.當有兩個線程開始競爭這個鎖對象,情況發生變化了,不再是偏向(獨占)鎖了,鎖會升級為輕量級鎖,兩個線程公平競爭,通過CAS對對象頭進行設置,哪個線程先占有鎖對象并執行代碼,鎖對象的Mark Word就執行哪個線程的棧幀中的鎖記錄。如下圖第三種情形。
4.如果競爭的這個鎖對象的線程更多,導致了更多的切換和等待,JVM會把該鎖對象的鎖升級為重量級鎖,這個就叫做同步鎖,這個鎖對象Mark Word再次發生變化,會指向一個監視器對象,這個監視器對象用集合的形式,來登記和管理排隊的線程。如下圖第四種情形。
6.2 Lock
6.3 CAS
6.4 volatile
用于保證變量的可見性與有序性,不保證原子性。
使用volatile修飾的變量,虛擬機將不會對指令重排序,同時值的讀寫將直接對主內存進行操作。
6.5 死鎖問題
死鎖即兩個或多個線程無限期相互等待鎖定資源的問題。
比如線程A鎖定資源1,等待資源2、線程B鎖定資源2,等待資源1,此時便形成死鎖。
6.5.1 形成死鎖的四個必要條件
- 互斥條件:線程(進程)對所分配的資源具有排它性,即一個資源只能被一個進程占用,直到該進程被釋放。
- 請求與保持條件:一個進程(線程)因請求被占有資源而發生堵塞時,對已獲取的資源保持不放。
- 不剝奪條件:線程(進程)已獲取的資源在未使用完之前不能被其他線程強行剝奪,只有等自己使用完才釋放資源。
- 循環等待條件:當發生死鎖時,所等待的線程(進程)必定形成一個環路,死循環造成永久堵塞。
6.5.2 如何避免死鎖
我們只需破壞形參死鎖的四個必要條件之一即可。
- 破壞請求與保持條件:一次申請所有資源
- 破壞不剝奪條件:占有部分資源的線程嘗試申請其它資源,如果申請不到,可以主動釋放它占有的資源。
- 破壞循環等待條件:按序來申請資源。
7. JDK并發工具
7.1?CyclicBarrier
7.2?CountDownLatch
7.3?Semaphore
8. 并發安全的集合
8.1 List
8.2 Map
8.3 Set
8.4 Queue
9. 原子工具類
總結
- 上一篇: 在IDEA中右键New没有创建Mappe
- 下一篇: JDK集合