【死磕 Java 併發】系列是 LZ 在 2017 年寫的第一個死磕系列,一直沒有作一個合集,這篇博客則是將整個系列作一個概覽。node
先來一個總覽圖:算法
【高清圖,請關注「Java技術驛站」公衆號,回覆:腦圖JUC】設計模式
synchronized 能夠保證方法或者代碼塊在運行時,同一時刻只有一個方法能夠進入到臨界區,同時它還能夠保證共享變量的內存可見性。深刻分析 synchronized 的內在實現機制,鎖優化、鎖升級過程。緩存
volatile 能夠保證線程可見性且提供了必定的有序性,可是沒法保證原子性。在 JVM 底層 volatile 是採用「內存屏障」來實現的。這篇博文將帶你分析 volatile 的本質數據結構
happens-before 原則是判斷數據是否存在競爭、線程是否安全的主要依據,保證了多線程環境下的可見性。併發
定義以下:app
在執行程序時,爲了提供性能,處理器和編譯器經常會對指令進行重排序,可是不能隨意重排序,不是你想怎麼排序就怎麼排序,它須要知足如下兩個條件:
as-if-serial 語義保證在單線程環境下重排序後的執行結果不會改變。
volatile的內存語義是:
老是說 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 內在本質。
讀寫鎖維護着一對鎖,一個讀鎖和一個寫鎖。經過分離讀鎖和寫鎖,使得併發性比通常的排他鎖有了較大的提高:在同一時間能夠容許多個讀線程同時訪問,可是在寫線程訪問時,全部讀線程和寫線程都會被阻塞。
讀寫鎖的主要特性:
在沒有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規定了以下幾個不變性:
咱們在Java世界裏看到了兩種實現key-value的數據結構:Hash、TreeMap,這兩種數據結構各自都有着優缺點。
這裏介紹第三種實現 key-value 的數據結構:SkipList。SkipList 有着不低於紅黑樹的效率,可是其原理和實現的複雜度要比紅黑樹簡單多了。
ConcurrentSkipListMap 其內部採用 SkipLis 數據結構實現。
ArrayBlockingQueue,一個由數組實現的有界阻塞隊列。該隊列採用FIFO的原則對元素進行排序添加的。
ArrayBlockingQueue 爲有界且固定,其大小在構造時由構造函數來決定,確認以後就不能再改變了。ArrayBlockingQueue 支持對等待的生產者線程和使用者線程進行排序的可選公平策略,可是在默認狀況下不保證線程公平的訪問,在構造時能夠選擇公平策略(fair = true)。公平性一般會下降吞吐量,可是減小了可變性和避免了「不平衡性」。
PriorityBlockingQueue是一個支持優先級的無界阻塞隊列。默認狀況下元素採用天然順序升序排序,固然咱們也能夠經過構造函數來指定Comparator來對元素進行排序。須要注意的是PriorityBlockingQueue不能保證同優先級元素的順序。
DelayQueue是一個支持延時獲取元素的無界阻塞隊列。裏面的元素所有都是「可延期」的元素,列頭的元素是最早「到期」的元素,若是隊列裏面沒有元素到期,是不能從列頭獲取元素的,哪怕有元素也不行。也就是說只有在延遲期到時纔可以從隊列中取元素。
DelayQueue主要用於兩個方面:
SynchronousQueue與其餘BlockingQueue有着不一樣特性:
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 中線程池的實現。
ScheduledThreadPoolExecutor 是實現線程的週期、延遲調度的。
ScheduledThreadPoolExecutor,繼承 ThreadPoolExecutor 且實現了 ScheduledExecutorService 接口,它就至關於提供了「延遲」和「週期執行」功能的 ThreadPoolExecutor。在JDK API中是這樣定義它的:ThreadPoolExecutor,它可另行安排在給定的延遲後運行命令,或者按期執行命令。須要多個輔助線程時,或者要求 ThreadPoolExecutor 具備額外的靈活性或功能時,此類要優於 Timer。 一旦啓用已延遲的任務就執行它,可是有關什麼時候啓用,啓用後什麼時候執行則沒有任何實時保證。按照提交的先進先出 (FIFO) 順序來啓用那些被安排在同一執行時間的任務。