【死磕Java並發】----- 死磕 Java 並發精品合集


【死磕 Java 並發】系列是 LZ 在 2017 年寫的第一個死磕系列,一直沒有做一個合集,這篇博客則是將整個系列做一個概覽。

先來一個總覽圖:

chenssy_juc_201712

【高清圖,請關注“Java技術驛站”公眾號,回復:腦圖JUC】

【死磕Java並發】—–深入分析synchronized 的實現原理

synchronized 可以保證方法或者代碼塊在運行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變量的內存可見性。深入分析 synchronized 的內在實現機制,鎖優化、鎖升級過程。

【死磕Java並發】—–深入分析volatile的實現原理

volatile 可以保證線程可見性且提供了一定的有序性,但是無法保證原子性。在 JVM 底層 volatile 是采用“內存屏障”來實現的。這篇博文將帶你分析 volatile 的本質

【死磕Java並發】—–Java內存模型之happens-before

happens-before 原則是判斷數據是否存在競爭、線程是否安全的主要依據,保證了多線程環境下的可見性。

定義如下:

  1. 如果一個操作happens-before另一個操作,那么第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。
  2. 兩個操作之間存在happens-before關系,並不意味着一定要按照happens-before原則制定的順序來執行。如果重排序之后的執行結果與按照happens-before關系來執行的結果一致,那么這種重排序並不非法。

【死磕Java並發】—–Java內存模型之重排序

在執行程序時,為了提供性能,處理器和編譯器常常會對指令進行重排序,但是不能隨意重排序,不是你想怎么排序就怎么排序,它需要滿足以下兩個條件:

  • 在單線程環境下不能改變程序運行的結果;
  • 存在數據依賴關系的不允許重排序

as-if-serial 語義保證在單線程環境下重排序后的執行結果不會改變。

【死磕Java並發】—–Java內存模型之分析volatile

volatile的內存語義是:

  • 當寫一個 volatile 變量時,JMM 會把該線程對應的本地內存中的共享變量值立即刷新到主內存中。
  • 當讀一個 volatile 變量時,JMM 會把該線程對應的本地內存設置為無效,直接從主內存中讀取共享變量

總是說 volatile 保證可見性,happens-before 是 JMM 實現可見性的基礎理論,兩者會碰撞怎樣的火花?這篇博文給你答案。

【死磕Java並發】—–Java內存模型之從JMM角度分析DCL

DCL,即Double Check Lock,雙重檢查鎖定。是實現單例模式比較好的方式,這篇博客告訴你 DCL 中為何要加 volatile 這個關鍵字。

【死磕Java並發】—–J.U.C之AQS:AQS簡介

AQS,AbstractQueuedSynchronizer,即隊列同步器。它是構建鎖或者其他同步組件的基礎框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),為 JUC 並發包中的核心基礎組件。

【死磕Java並發】—–J.U.C之AQS:CLH同步隊列

前線程已經等待狀態等信息構造成一個節點(Node)並將其加入到CLH同步隊列,同時會阻塞當前線程,當同步狀態釋放時,會把首節點喚醒(公平鎖),使其再次嘗試獲取同步狀態。

【死磕Java並發】—–J.U.C之AQS:同步狀態的獲取與釋放

AQS的設計模式采用的模板方法模式,子類通過繼承的方式,實現它的抽象方法來管理同步狀態,對於子類而言它並沒有太多的活要做,AQS提供了大量的模板方法來實現同步,主要是分為三類:獨占式獲取和釋放同步狀態、共享式獲取和釋放同步狀態、查詢同步隊列中的等待線程情況。

【死磕Java並發】—–J.U.C之AQS:阻塞和喚醒線程

當需要阻塞或者喚醒一個線程的時候,AQS 都是使用 LockSupport 這個工具類來完成。

LockSupport是用來創建鎖和其他同步類的基本線程阻塞原語。

【死磕Java並發】—–J.U.C之重入鎖:ReentrantLock

一個可重入的互斥鎖定 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖定相同的一些基本行為和語義,但功能更強大。ReentrantLock 將由最近成功獲得鎖定,並且還沒有釋放該鎖定的線程所擁有。當鎖定沒有被另一個線程所擁有時,調用 lock 的線程將成功獲取該鎖定並返回。如果當前線程已經擁有該鎖定,此方法將立即返回。可以使用 isHeldByCurrentThread()getHoldCount() 方法來檢查此情況是否發生。

這篇博客帶你理解 重入鎖:ReentrantLock 內在本質。

【死磕Java並發】—–J.U.C之讀寫鎖:ReentrantReadWriteLock

讀寫鎖維護着一對鎖,一個讀鎖和一個寫鎖。通過分離讀鎖和寫鎖,使得並發性比一般的排他鎖有了較大的提升:在同一時間可以允許多個讀線程同時訪問,但是在寫線程訪問時,所有讀線程和寫線程都會被阻塞。

讀寫鎖的主要特性:

  • 公平性:支持公平性和非公平性。
  • 重入性:支持重入。讀寫鎖最多支持65535個遞歸寫入鎖和65535個遞歸讀取鎖。
  • 鎖降級:遵循獲取寫鎖、獲取讀鎖在釋放寫鎖的次序,寫鎖能夠降級成為讀鎖

【死磕Java並發】—–J.U.C之Condition

在沒有Lock之前,我們使用synchronized來控制同步,配合Object的wait()、notify()系列方法可以實現等待/通知模式。在Java SE5后,Java提供了Lock接口,相對於Synchronized而言,Lock提供了條件Condition,對線程的等待、喚醒操作更加詳細和靈活

【死磕Java並發】—-深入分析CAS

CAS,Compare And Swap,即比較並交換。Doug lea大神在同步組件中大量使用 CAS 技術鬼斧神工地實現了Java 多線程的並發操作。整個 AQS 同步組件、Atomic 原子類操作等等都是以 CAS 實現的。可以說CAS是整個JUC的基石。

【死磕Java並發】—–J.U.C之並發工具類:CyclicBarrier

CyclicBarrier,一個同步輔助類。它允許一組線程互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的線程的程序中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 barrier 在釋放等待線程后可以重用,所以稱它為循環 的 barrier。

【死磕Java並發】—–J.U.C之並發工具類:CountDownLatch

CountDownLatch 所描述的是”在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待“。

用給定的計數 初始化 CountDownLatch。由於調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。之后,會釋放所有等待的線程,await 的所有后續調用都將立即返回。

【死磕Java並發】—–J.U.C之並發工具類:Semaphore

Semaphore,信號量,是一個控制訪問多個共享資源的計數器。從概念上講,信號量維護了一個許可集。如有必要,在許可可用前會阻塞每一個 acquire(),然后再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,Semaphore 只對可用許可的號碼進行計數,並采取相應的行動。

【死磕Java並發】—–J.U.C之並發工具類:Exchanger

可以在對中對元素進行配對和交換的線程的同步點。每個線程將條目上的某個方法呈現給 exchange 方法,與伙伴線程進行匹配,並且在返回時接收其伙伴的對象。Exchanger 可能被視為 SynchronousQueue 的雙向形式。Exchanger 可能在應用程序(比如遺傳算法和管道設計)中很有用。

【死磕Java並發】—–J.U.C之Java並發容器:ConcurrentHashMap

ConcurrentHashMap 作為 Concurrent 一族,其有着高效地並發操作。在1.8 版本以前,ConcurrentHashMap 采用分段鎖的概念,使鎖更加細化,但是 1.8 已經改變了這種思路,而是利用 CAS + Synchronized 來保證並發更新的安全,當然底層采用數組+鏈表+紅黑樹的存儲結構。這篇博客帶你徹底理解 ConcurrentHashMap。

【死磕Java並發】—–J.U.C之ConcurrentHashMap紅黑樹轉換分析

在 1.8 ConcurrentHashMap 的put操作中,如果發現鏈表結構中的元素超過了TREEIFY_THRESHOLD(默認為8),則會把鏈表轉換為紅黑樹,已便於提高查詢效率。那么具體的轉換過程是怎么樣的?這篇博客給你答案。

【死磕Java並發】—–J.U.C之Java並發容器:ConcurrentLinkedQueue

ConcurrentLinkedQueue是一個基於鏈接節點的無邊界的線程安全隊列,它采用FIFO原則對元素進行排序。采用“wait-free”算法(即CAS算法)來實現的。

CoucurrentLinkedQueue規定了如下幾個不變性:

  1. 在入隊的最后一個元素的next為null
  2. 隊列中所有未刪除的節點的item都不能為null且都能從head節點遍歷到
  3. 對於要刪除的節點,不是直接將其設置為null,而是先將其item域設置為null(迭代器會跳過item為null的節點)
  4. 允許head和tail更新滯后。這是什么意思呢?意思就說是head、tail不總是指向第一個元素和最后一個元素(后面闡述)。

【死磕Java並發】—–J.U.C之Java並發容器:ConcurrentSkipListMap

我們在Java世界里看到了兩種實現key-value的數據結構:Hash、TreeMap,這兩種數據結構各自都有着優缺點。

  • Hash表:插入、查找最快,為O(1);如使用鏈表實現則可實現無鎖;數據有序化需要顯式的排序操作。
  • 紅黑樹:插入、查找為O(logn),但常數項較小;無鎖實現的復雜性很高,一般需要加鎖;數據天然有序。

這里介紹第三種實現 key-value 的數據結構:SkipList。SkipList 有着不低於紅黑樹的效率,但是其原理和實現的復雜度要比紅黑樹簡單多了。

ConcurrentSkipListMap 其內部采用 SkipLis 數據結構實現。

【死磕Java並發】—–J.U.C之阻塞隊列:ArrayBlockingQueue

ArrayBlockingQueue,一個由數組實現的有界阻塞隊列。該隊列采用FIFO的原則對元素進行排序添加的。

ArrayBlockingQueue 為有界且固定,其大小在構造時由構造函數來決定,確認之后就不能再改變了。ArrayBlockingQueue 支持對等待的生產者線程和使用者線程進行排序的可選公平策略,但是在默認情況下不保證線程公平的訪問,在構造時可以選擇公平策略(fair = true)。公平性通常會降低吞吐量,但是減少了可變性和避免了“不平衡性”。

【死磕Java並發】—–J.U.C之阻塞隊列:PriorityBlockingQueue

PriorityBlockingQueue是一個支持優先級的無界阻塞隊列。默認情況下元素采用自然順序升序排序,當然我們也可以通過構造函數來指定Comparator來對元素進行排序。需要注意的是PriorityBlockingQueue不能保證同優先級元素的順序。

【死磕Java並發】—–J.U.C之阻塞隊列:DelayQueue

DelayQueue是一個支持延時獲取元素的無界阻塞隊列。里面的元素全部都是“可延期”的元素,列頭的元素是最先“到期”的元素,如果隊列里面沒有元素到期,是不能從列頭獲取元素的,哪怕有元素也不行。也就是說只有在延遲期到時才能夠從隊列中取元素。

DelayQueue主要用於兩個方面:

  • 緩存:清掉緩存中超時的緩存數據
  • 任務超時處理

【死磕Java並發】—–J.U.C之阻塞隊列:SynchronousQueue

SynchronousQueue與其他BlockingQueue有着不同特性:

  1. SynchronousQueue沒有容量。與其他BlockingQueue不同,SynchronousQueue是一個不存儲元素的BlockingQueue。每一個put操作必須要等待一個take操作,否則不能繼續添加元素,反之亦然。
  2. 因為沒有容量,所以對應 peek, contains, clear, isEmpty ... 等方法其實是無效的。例如clear是不執行任何操作的,contains始終返回false,peek始終返回null。
  3. SynchronousQueue分為公平和非公平,默認情況下采用非公平性訪問策略,當然也可以通過構造函數來設置為公平性訪問策略(為true即可)。
  4. 若使用 TransferQueue, 則隊列中永遠會存在一個 dummy node(這點后面詳細闡述)。

SynchronousQueue非常適合做交換工作,生產者的線程和消費者的線程同步以傳遞某些信息、事件或者任務。

【死磕Java並發】—–J.U.C之阻塞隊列:LinkedTransferQueue

LinkedTransferQueue 是基於鏈表的 FIFO 無界阻塞隊列,它出現在 JDK7 中。Doug Lea 大神說 LinkedTransferQueue 是一個聰明的隊列。它是 ConcurrentLinkedQueue、SynchronousQueue (公平模式下)、無界的LinkedBlockingQueues 等的超集。

【死磕Java並發】—–J.U.C之阻塞隊列:LinkedBlockingDeque

LinkedBlockingDeque 是一個由鏈表組成的雙向阻塞隊列,雙向隊列就意味着可以從對頭、對尾兩端插入和移除元素,同樣意味着 LinkedBlockingDeque 支持 FIFO、FILO 兩種操作方式。

LinkedBlockingDeque 是可選容量的,在初始化時可以設置容量防止其過度膨脹,如果不設置,默認容量大小為 Integer.MAX_VALUE。

【死磕Java並發】—–深入分析ThreadLocal

ThreadLocal 提供了線程局部 (thread-local) 變量。這些變量不同於它們的普通對應物,因為訪問某個變量(通過其get 或 set 方法)的每個線程都有自己的局部變量,它獨立於變量的初始化副本。ThreadLocal實例通常是類中的 private static 字段,它們希望將狀態與某一個線程(例如,用戶 ID 或事務 ID)相關聯。

所以ThreadLocal與線程同步機制不同,線程同步機制是多個線程共享同一個變量,而ThreadLocal是為每一個線程創建一個單獨的變量副本,故而每個線程都可以獨立地改變自己所擁有的變量副本,而不會影響其他線程所對應的副本。可以說ThreadLocal為多線程環境下變量問題提供了另外一種解決思路。

【死磕Java並發】—–J.U.C之線程池:ThreadPoolExecutor

鼎鼎大名的線程池。不需要多說!!!!!

這篇博客深入分析 Java 中線程池的實現。

【死磕Java並發】—–J.U.C之線程池:ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor 是實現線程的周期、延遲調度的。

ScheduledThreadPoolExecutor,繼承 ThreadPoolExecutor 且實現了 ScheduledExecutorService 接口,它就相當於提供了“延遲”和“周期執行”功能的 ThreadPoolExecutor。在JDK API中是這樣定義它的:ThreadPoolExecutor,它可另行安排在給定的延遲后運行命令,或者定期執行命令。需要多個輔助線程時,或者要求 ThreadPoolExecutor 具有額外的靈活性或功能時,此類要優於 Timer。 一旦啟用已延遲的任務就執行它,但是有關何時啟用,啟用后何時執行則沒有任何實時保證。按照提交的先進先出 (FIFO) 順序來啟用那些被安排在同一執行時間的任務。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM