點選上方“芋道原始碼”,選擇“置頂公眾號”
技術文章第一時間送達!
原始碼精品專欄
【死磕 Java 併發】系列是 LZ 在 2017 年寫的第一個死磕系列,一直沒有做一個合集,這篇部落格則是將整個系列做一個概覽。
先來一個總覽圖:
【高畫質圖,請關註“Java技術驛站”公眾號,回覆:腦圖JUC】
synchronized 可以保證方法或者程式碼塊在執行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變數的記憶體可見性。深入分析 synchronized 的內在實現機制,鎖最佳化、鎖升級過程。
volatile 可以保證執行緒可見性且提供了一定的有序性,但是無法保證原子性。在 JVM 底層 volatile 是採用“記憶體屏障”來實現的。這篇博文將帶你分析 volatile 的本質
happens-before 原則是判斷資料是否存在競爭、執行緒是否安全的主要依據,保證了多執行緒環境下的可見性。
定義如下:
-
如果一個操作happens-before另一個操作,那麼第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
-
兩個操作之間存在happens-before關係,並不意味著一定要按照happens-before原則制定的順序來執行。如果重排序之後的執行結果與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法。
在執行程式時,為了提供效能,處理器和編譯器常常會對指令進行重排序,但是不能隨意重排序,不是你想怎麼排序就怎麼排序,它需要滿足以下兩個條件:
-
在單執行緒環境下不能改變程式執行的結果;
-
存在資料依賴關係的不允許重排序
as-if-serial 語意保證在單執行緒環境下重排序後的執行結果不會改變。
volatile的記憶體語意是:
-
當寫一個 volatile 變數時,JMM 會把該執行緒對應的本地記憶體中的共享變數值立即掃清到主記憶體中。
-
當讀一個 volatile 變數時,JMM 會把該執行緒對應的本地記憶體設定為無效,直接從主記憶體中讀取共享變數
總是說 volatile 保證可見性,happens-before 是 JMM 實現可見性的基礎理論,兩者會碰撞怎樣的火花?這篇博文給你答案。
DCL,即Double Check Lock,雙重檢查鎖定。是實現單例樣式比較好的方式,這篇部落格告訴你 DCL 中為何要加 volatile 這個關鍵字。
AQS,AbstractQueuedSynchronizer,即佇列同步器。它是構建鎖或者其他同步元件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),為 JUC 併發包中的核心基礎元件。
前執行緒已經等待狀態等資訊構造成一個節點(Node)並將其加入到CLH同步佇列,同時會阻塞當前執行緒,當同步狀態釋放時,會把首節點喚醒(公平鎖),使其再次嘗試獲取同步狀態。
AQS的設計樣式採用的模板方法樣式,子類透過繼承的方式,實現它的抽象方法來管理同步狀態,對於子類而言它並沒有太多的活要做,AQS提供了大量的模板方法來實現同步,主要是分為三類:獨佔式獲取和釋放同步狀態、共享式獲取和釋放同步狀態、查詢同步佇列中的等待執行緒情況。
當需要阻塞或者喚醒一個執行緒的時候,AQS 都是使用 LockSupport 這個工具類來完成。
LockSupport是用來建立鎖和其他同步類的基本執行緒阻塞原語。
一個可重入的互斥鎖定 Lock,它具有與使用 synchronized 方法和陳述句所訪問的隱式監視器鎖定相同的一些基本行為和語意,但功能更強大。ReentrantLock 將由最近成功獲得鎖定,並且還沒有釋放該鎖定的執行緒所擁有。當鎖定沒有被另一個執行緒所擁有時,呼叫 lock 的執行緒將成功獲取該鎖定並傳回。如果當前執行緒已經擁有該鎖定,此方法將立即傳回。可以使用 isHeldByCurrentThread()
和 getHoldCount()
方法來檢查此情況是否發生。
這篇部落格帶你理解 重入鎖:ReentrantLock 內在本質。
讀寫鎖維護著一對鎖,一個讀鎖和一個寫鎖。透過分離讀鎖和寫鎖,使得併發性比一般的排他鎖有了較大的提升:在同一時間可以允許多個讀執行緒同時訪問,但是在寫執行緒訪問時,所有讀執行緒和寫執行緒都會被阻塞。
讀寫鎖的主要特性:
-
公平性:支援公平性和非公平性。
-
重入性:支援重入。讀寫鎖最多支援65535個遞迴寫入鎖和65535個遞迴讀取鎖。
-
鎖降級:遵循獲取寫鎖、獲取讀鎖在釋放寫鎖的次序,寫鎖能夠降級成為讀鎖
在沒有Lock之前,我們使用synchronized來控制同步,配合Object的wait()、notify()系列方法可以實現等待/通知樣式。在Java SE5後,Java提供了Lock介面,相對於Synchronized而言,Lock提供了條件Condition,對執行緒的等待、喚醒操作更加詳細和靈活
CAS,Compare And Swap,即比較並交換。Doug lea大神在同步元件中大量使用 CAS 技術鬼斧神工地實現了Java 多執行緒的併發操作。整個 AQS 同步元件、Atomic 原子類操作等等都是以 CAS 實現的。可以說CAS是整個JUC的基石。
CyclicBarrier,一個同步輔助類。它允許一組執行緒互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的執行緒的程式中,這些執行緒必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 barrier 在釋放等待執行緒後可以重用,所以稱它為迴圈 的 barrier。
CountDownLatch 所描述的是”在完成一組正在其他執行緒中執行的操作之前,它允許一個或多個執行緒一直等待“。
用給定的計數 初始化 CountDownLatch。由於呼叫了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之後,會釋放所有等待的執行緒,await 的所有後續呼叫都將立即傳回。
Semaphore,訊號量,是一個控制訪問多個共享資源的計數器。從概念上講,訊號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release() 新增一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可物件,Semaphore 只對可用許可的號碼進行計數,並採取相應的行動。
可以在對中對元素進行配對和交換的執行緒的同步點。每個執行緒將條目上的某個方法呈現給 exchange 方法,與夥伴執行緒進行匹配,並且在傳回時接收其夥伴的物件。Exchanger 可能被視為 SynchronousQueue 的雙向形式。Exchanger 可能在應用程式(比如遺傳演演算法和管道設計)中很有用。
ConcurrentHashMap 作為 Concurrent 一族,其有著高效地併發操作。在1.8 版本以前,ConcurrentHashMap 採用分段鎖的概念,使鎖更加細化,但是 1.8 已經改變了這種思路,而是利用 CAS + Synchronized 來保證併發更新的安全,當然底層採用陣列+連結串列+紅黑樹的儲存結構。這篇部落格帶你徹底理解 ConcurrentHashMap。
在 1.8 ConcurrentHashMap 的put操作中,如果發現連結串列結構中的元素超過了TREEIFY_THRESHOLD(預設為8),則會把連結串列轉換為紅黑樹,已便於提高查詢效率。那麼具體的轉換過程是怎麼樣的?這篇部落格給你答案。
ConcurrentLinkedQueue是一個基於連結節點的無邊界的執行緒安全佇列,它採用FIFO原則對元素進行排序。採用“wait-free”演演算法(即CAS演演算法)來實現的。
CoucurrentLinkedQueue規定瞭如下幾個不變性:
-
在入隊的最後一個元素的next為null
-
佇列中所有未刪除的節點的item都不能為null且都能從head節點遍歷到
-
對於要刪除的節點,不是直接將其設定為null,而是先將其item域設定為null(迭代器會跳過item為null的節點)
-
允許head和tail更新滯後。這是什麼意思呢?意思就說是head、tail不總是指向第一個元素和最後一個元素(後面闡述)。
我們在Java世界裡看到了兩種實現key-value的資料結構:Hash、TreeMap,這兩種資料結構各自都有著優缺點。
-
Hash表:插入、查詢最快,為O(1);如使用連結串列實現則可實現無鎖;資料有序化需要顯式的排序操作。
-
紅黑樹:插入、查詢為O(logn),但常數項較小;無鎖實現的複雜性很高,一般需要加鎖;資料天然有序。
這裡介紹第三種實現 key-value 的資料結構:SkipList。SkipList 有著不低於紅黑樹的效率,但是其原理和實現的複雜度要比紅黑樹簡單多了。
ConcurrentSkipListMap 其內部採用 SkipLis 資料結構實現。
ArrayBlockingQueue,一個由陣列實現的有界阻塞佇列。該佇列採用FIFO的原則對元素進行排序新增的。
ArrayBlockingQueue 為有界且固定,其大小在構造時由建構式來決定,確認之後就不能再改變了。ArrayBlockingQueue 支援對等待的生產者執行緒和使用者執行緒進行排序的可選公平策略,但是在預設情況下不保證執行緒公平的訪問,在構造時可以選擇公平策略(fair = true)。公平性通常會降低吞吐量,但是減少了可變性和避免了“不平衡性”。
PriorityBlockingQueue是一個支援優先順序的無界阻塞佇列。預設情況下元素採用自然順序升序排序,當然我們也可以透過建構式來指定Comparator來對元素進行排序。需要註意的是PriorityBlockingQueue不能保證同優先順序元素的順序。
DelayQueue是一個支援延時獲取元素的無界阻塞佇列。裡面的元素全部都是“可延期”的元素,列頭的元素是最先“到期”的元素,如果佇列裡面沒有元素到期,是不能從列頭獲取元素的,哪怕有元素也不行。也就是說只有在延遲期到時才能夠從佇列中取元素。
DelayQueue主要用於兩個方面:
-
快取:清掉快取中超時的快取資料
-
任務超時處理
SynchronousQueue與其他BlockingQueue有著不同特性:
-
SynchronousQueue沒有容量。與其他BlockingQueue不同,SynchronousQueue是一個不儲存元素的BlockingQueue。每一個put操作必須要等待一個take操作,否則不能繼續新增元素,反之亦然。
-
因為沒有容量,所以對應 peek, contains, clear, isEmpty … 等方法其實是無效的。例如clear是不執行任何操作的,contains始終傳回false,peek始終傳回null。
-
SynchronousQueue分為公平和非公平,預設情況下採用非公平性訪問策略,當然也可以透過建構式來設定為公平性訪問策略(為true即可)。
-
若使用 TransferQueue, 則佇列中永遠會存在一個 dummy node(這點後面詳細闡述)。
SynchronousQueue非常適合做交換工作,生產者的執行緒和消費者的執行緒同步以傳遞某些資訊、事件或者任務。
LinkedTransferQueue 是基於連結串列的 FIFO 無界阻塞佇列,它出現在 JDK7 中。Doug Lea 大神說 LinkedTransferQueue 是一個聰明的佇列。它是 ConcurrentLinkedQueue、SynchronousQueue (公平樣式下)、無界的LinkedBlockingQueues 等的超集。
LinkedBlockingDeque 是一個由連結串列組成的雙向阻塞佇列,雙向佇列就意味著可以從對頭、對尾兩端插入和移除元素,同樣意味著 LinkedBlockingDeque 支援 FIFO、FILO 兩種操作方式。
LinkedBlockingDeque 是可選容量的,在初始化時可以設定容量防止其過度膨脹,如果不設定,預設容量大小為 Integer.MAX_VALUE。
ThreadLocal 提供了執行緒區域性 (thread-local) 變數。這些變數不同於它們的普通對應物,因為訪問某個變數(透過其get 或 set 方法)的每個執行緒都有自己的區域性變數,它獨立於變數的初始化副本。ThreadLocal實體通常是類中的 private static 欄位,它們希望將狀態與某一個執行緒(例如,使用者 ID 或事務 ID)相關聯。
所以ThreadLocal與執行緒同步機制不同,執行緒同步機制是多個執行緒共享同一個變數,而ThreadLocal是為每一個執行緒建立一個單獨的變數副本,故而每個執行緒都可以獨立地改變自己所擁有的變數副本,而不會影響其他執行緒所對應的副本。可以說ThreadLocal為多執行緒環境下變數問題提供了另外一種解決思路。
鼎鼎大名的執行緒池。不需要多說!!!!!
這篇部落格深入分析 Java 中執行緒池的實現。
【死磕Java併發】—–J.U.C之執行緒池:ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor 是實現執行緒的週期、延遲排程的。
ScheduledThreadPoolExecutor,繼承 ThreadPoolExecutor 且實現了 ScheduledExecutorService 介面,它就相當於提供了“延遲”和“週期執行”功能的 ThreadPoolExecutor。在JDK API中是這樣定義它的:ThreadPoolExecutor,它可另行安排在給定的延遲後執行命令,或者定期執行命令。需要多個輔助執行緒時,或者要求 ThreadPoolExecutor 具有額外的靈活性或功能時,此類要優於 Timer。 一旦啟用已延遲的任務就執行它,但是有關何時啟用,啟用後何時執行則沒有任何實時保證。按照提交的先進先出 (FIFO) 順序來啟用那些被安排在同一執行時間的任務。
如果你對 Dubbo 感興趣,歡迎加入我的知識星球一起交流。
目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:
01. 除錯環境搭建
02. 專案結構一覽
03. 配置 Configuration
04. 核心流程一覽
05. 拓展機制 SPI
06. 執行緒池
07. 服務暴露 Export
08. 服務取用 Refer
09. 註冊中心 Registry
10. 動態編譯 Compile
11. 動態代理 Proxy
12. 服務呼叫 Invoke
13. 呼叫特性
14. 過濾器 Filter
15. NIO 伺服器
16. P2P 伺服器
17. HTTP 伺服器
18. 序列化 Serialization
19. 叢集容錯 Cluster
20. 優雅停機
21. 日誌適配
22. 狀態檢查
23. 監控中心 Monitor
24. 管理中心 Admin
25. 運維命令 QOS
26. 鏈路追蹤 Tracing
…
一共 60 篇++