本文部份內容摘自:php
threadlocal,在實際項目中應用:好比:保存本次請求用戶的信息、logId或者一些須要在一次request時須要都用的數據
html
參考:java
Java併發編程系列:線程的五大狀態,以及線程之間的通訊與協做node
Java 線程的 5 種狀態android
sleep、wait、notify、yield、join關係與區別:參考面試
- AQS的實現依賴內部的同步隊列,也就是FIFO的雙向隊列,若是當前線程競爭鎖失敗,那麼AQS會把當前線程以及等待狀態信息構形成一個Node加入到同步隊列中,同時再阻塞該線程。當獲取鎖的線程釋放鎖之後,會從隊列中喚醒一個阻塞的節點(線程)。
- AQS隊列內部維護的是一個FIFO的雙向鏈表,這種結構的特色是每一個數據結構都有兩個指針,分別指向直接的後繼節點和直接前驅節點。因此雙向鏈表能夠從任意一個節點開始很方便的訪問前驅和後繼。每一個Node實際上是由線程封裝,當線程爭搶鎖失敗後會封裝成Node加入到AQS隊列中去
AQS的兩種功能編程
從使用層面來講,AQS的功能分爲兩種:獨佔和共享數組
獨佔鎖,每次只能有一個線程持有鎖,好比前面給你們演示的ReentrantLock就是以獨佔方式實現的互斥鎖安全
共享鎖,容許多個線程同時獲取鎖,併發訪問共享資源,好比ReentrantReadWriteLockbash
相關方法屬性
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer{
//指向同步隊列隊頭
private transient volatile Node head;
//指向同步的隊尾
private transient volatile Node tail;
//同步狀態,0表明鎖未被佔用,1表明鎖已被佔用
private volatile int state;
}複製代碼
AQS內部有一個同步隊列,它是由Node組成的雙向鏈表結構
AQS內部經過state來控制同步狀態,當執行lock時,若是state=0時,說明沒有任何線程佔有共享資源的鎖,此時線程會獲取到鎖並把state設置爲1;當state=1時,則說明有線程目前正在使用共享變量,其餘線程必須加入同步隊列進行等待.
AQS內部分爲共享模式(如Semaphore)和獨佔模式(如Reentrantlock),不管是共享模式仍是獨佔模式的實現類,都維持着一個虛擬的同步隊列,當請求鎖的線程超過現有模式的限制時,會將線程包裝成Node結點並將線程當前必要的信息存儲到node結點中,而後加入同步隊列等會獲取鎖,而這系列操做都有AQS協助咱們完成,這也是做爲基礎組件的緣由,不管是Semaphore仍是Reentrantlock,其內部絕大多數方法都是間接調用AQS完成的。
當出現鎖競爭以及釋放鎖的時候,AQS同步隊列中的節點會發生變化,首先看一下添加節點的場景。裏會涉及到兩個變化
新的線程封裝成Node節點追加到同步隊列中,設置prev節點以及修改當前節點的前置節點的next節點指向本身
經過CAS講tail從新指向新的尾部節點
head節點表示獲取鎖成功的節點,當頭結點在釋放同步狀態時,會喚醒後繼節點,若是後繼節點得到鎖成功,會把本身設置爲頭結點,節點的變化過程以下
這個過程也是涉及到兩個變化
修改head節點指向下一個得到鎖的節點
新的得到鎖的節點,將prev的指針指向null
這裏有一個小的變化,就是設置head節點不須要用CAS,緣由是設置head節點是由得到鎖的線程來完成的,而同步鎖只能由一個線程得到,因此不須要CAS保證,只須要把head節點設置爲原首節點的後繼節點,而且斷開原head節點的next引用便可
ReentrantLock.lock()public void lock() {
sync.lock();
}複製代碼
這個是獲取鎖的入口,調用sync這個類裏面的方法,sync是什麼呢?
sync是一個靜態內部類,它繼承了AQS這個抽象類,前面說過AQS是一個同步工具,主要用來實現同步控制。咱們在利用這個工具的時候,會繼承它來實現同步控制功能。 經過進一步分析,發現Sync這個類有兩個具體的實現,分別是 NofairSync(非公平鎖)
, FailSync(公平鎖)
.
公平鎖 表示全部線程嚴格按照FIFO來獲取鎖
非公平鎖 表示能夠存在搶佔鎖的功能,也就是說無論當前隊列上是否存在其餘線程等待,新線程都有機會搶佔鎖
ReentrantLock內部包含了一個AQS對象,也就是AbstractQueuedSynchronizer類型的對象。這個AQS對象就是ReentrantLock能夠實現加鎖和釋放鎖的關鍵性的核心組件。
AQS內部有個變量 state ,是int類型的,表明了 加鎖的狀態 。初始狀態下,state值是0。
AQS內部還有個 變量 ,用來記錄 當前加鎖的是哪一個線程 ,初始化狀態下,變量是null。
AQS內部還有一個等待隊列,專門放那些加鎖 敗的線程!
如何進行可重入加鎖! 其實每次線程1可重入加鎖一次,會判斷一下當前加鎖線程就是本身,那麼他本身就能夠可重入屢次加鎖,每次加鎖就是把state的值給累加1,別的沒變化
AQS是如此的核心!AQS內部還有一個等待隊列,專門放那些加鎖失敗的線程!
本題轉自 :公衆號來源:孤獨煙
API方面: synchronized既能夠修飾方法,也能夠修飾代碼塊。ReentrantLock只能在方法體中使用。
公平鎖: synchronized的鎖是非公平鎖,ReentrantLock默認狀況下也是非公平鎖,但能夠經過帶布爾值的構造函數要求使用公平鎖。
等待可中斷: 假如業務代碼中有兩個線程,Thread1 Thread2。假設 Thread1 獲取了對象object的鎖,Thread2將等待Thread1釋放object的鎖。
- 使用synchronized。若是Thread1不釋放,Thread2將一直等待,不能被中斷。synchronized也能夠說是Java提供的原子性內置鎖機制。內部鎖扮演了互斥鎖(mutual exclusion lock ,mutex)的角色,一個線程引用鎖的時候,別的線程阻塞等待。
使用ReentrantLock。若是Thread1不釋放,Thread2等待了很長時間之後,能夠中斷等待,轉而去作別的事情。
至於判斷重入鎖, ReenTrantLock的字面意思就是再進入的鎖,其實synchronized關鍵字所使用的鎖也是可重入的,二者關於這個的區別不大。二者都是同一個線程沒進入一次,鎖的計數器都自增1,因此要等到鎖的計數器降低爲0時才能釋放鎖。
擴展:hashset,hashtable
HashSet底層使用了哈希表來支持的,特色:存儲快
哈希表的出現是爲了解決鏈表訪問不快速的弱點,哈希表也稱散列表。
HashSet是經過HasMap來實現的,HashMap的輸入參數有Key、Value兩個組成,在實現HashSet時,保持HashMap的Value爲常量,至關於在HashMap中只對Key對象進行處理。
往HashSet添加元素的時候,HashSet會先調用元素的hashCode方法獲得元素的哈希值 ,
而後經過元素的哈希值通過移位等運算,就能夠算出該元素在哈希表中的存儲位置。
狀況1: 若是算出元素存儲的位置目前沒有任何元素存儲,那該元素能夠直接存儲到該位置上
狀況2: 若是算出該元素的存儲位置目前已經存在有其餘的元素了,那麼會調用該元素的equals方法與該位置的元素再比較一次,若是equals返回的是true,那麼該元素與這個位置上的元素就視爲重複元素,不容許添加,若是equals方法返回的是false,那麼該元素運行添加。
因此說,當數組長度爲2的n次冪的時候,不一樣的key算得得index相同的概率較小,那麼數據在數組上分佈就比較均勻,也就是說碰撞的概率小,相對的,查詢的時候就不用遍歷某個位置上的鏈表,這樣查詢效率也就較高了。說到這裏,咱們再回頭看一下hashmap中默認的數組大小是多少,查看源代碼能夠得知是16,爲何是16,而不是15,也不是20呢,看到上面的解釋以後咱們就清楚了吧,顯然是由於16是2的整數次冪的緣由,在小數據量的狀況下16比15和20更能減小key之間的碰撞,而加快查詢的效率。
摘自:JDK1.8中的實現
ConcurrentHashMap取消了segment分段鎖,而採用CAS和synchronized來保證併發安全。數據結構跟HashMap1.8的結構同樣,數組+鏈表/紅黑二叉樹。
synchronized只鎖定當前鏈表或紅黑二叉樹的首節點,這樣只要hash不衝突,就不會產生併發,效率又提高N倍。TreeBin: 紅黑二叉樹節點,Node: 鏈表節點。
總結:
JDK8中的實現也是鎖分離的思想,它把鎖分的比segment更細一些,只要hash不衝突,就不會出現併發得到鎖的狀況。它首先使用無鎖操做CAS插入頭結點,若是插入失敗,說明已經有別的線程插入頭結點了,再次循環進行操做。若是頭結點已經存在,則經過synchronized得到頭結點鎖,進行後續的操做。性能比segment分段鎖又再次提高。
參見:Java併發包--ConcurrentLinkedQueue ,
Java併發編程之ConcurrentLinkedQueue詳解
ConcurrentLinkedQueue是一個基於連接節點的無界線程安全隊列,它採用FIFO先進先出的規則對節點進行排序,當咱們添加一個元素的時候,它會添加到隊列的尾部,當咱們獲取一個元素時,它會返回隊列頭部的元素。
ConcurrentLinkedQueue的數據結構,以下圖所示:
說明:
1. ConcurrentLinkedQueue繼承於AbstractQueue。
2. ConcurrentLinkedQueue內部是經過鏈表來實現的。它同時包含鏈表的頭節點head和尾節點tail。ConcurrentLinkedQueue按照 FIFO(先進先出)原則對元素進行排序。元素都是從尾部插入到鏈表,從頭部開始返回。
3. ConcurrentLinkedQueue的鏈表Node中的next的類型是volatile,並且鏈表數據item的類型也是volatile。關於volatile,咱們知道它的語義包含:「即對一個volatile變量的讀,老是能看到(任意線程)對這個volatile變量最後的寫入」。ConcurrentLinkedQueue就是經過volatile來實現多線程對競爭資源的互斥訪問的。
摘自:阻塞隊列及實現原理
三種阻塞隊列:
BlockingQueue<Runnable> workQueue = null;
workQueue = new ArrayBlockingQueue<>(5); //基於數組的先進先出隊列,有界
workQueue = new LinkedBlockingQueue<>(); //基於鏈表的先進先出隊列,無界
workQueue = new SynchronousQueue<>(); //無緩衝的等待隊列,無界
一、ArrayBlockingQueue是一個用數組實現的有界阻塞隊列。此隊列按照先進先出(FIFO)的原則對元素進行排序。默認狀況下不保證訪問者公平的訪問隊列,
訪問者的公平性是使用可重入鎖實現的,代碼以下:
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}複製代碼
二、LinkedBlockingQueue是一個用鏈表實現的有界阻塞隊列。此隊列的默認和最大長度爲Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序。
三、SynchronousQueue是一個不存儲元素的阻塞隊列。每個put操做必須等待一個take操做,不然不能繼續添加元素。SynchronousQueue能夠當作是一個傳球手,負責把生產者線程處理的數據直接傳遞給消費者線程。隊列自己並不存儲任何元素,很是適合於傳遞性場景,好比在一個線程中使用的數據,傳遞給另一個線程使用,SynchronousQueue的吞吐量高於LinkedBlockingQueue 和 ArrayBlockingQueue。
總結:
CountDownLatch、Semaphore和CyclicBarrier
參考:Java併發之CountDownLatch、Semaphore和CyclicBarrier
CountDownLatch:
.await(long, TimeUnit); 等待超時,針對某些業務場景,若是某一個線程的操做耗時很是長或者發生了異常. 可是並不想影響主線程的繼續執行, 則可使用await(long, TimeUnit)方法. 即一個線程(或者多個線程),等待另外n個線程執行long時間後繼續執行.
synchronized :重量級操做,基於悲觀鎖,可重入鎖。
AtomicInteger:樂觀 ,用CAS實現
incrementAndGet()方法在一個無限循環體內,不斷嘗試將一個比當前值大1的新值賦給本身,若是失敗則說明在執行"獲取-設置"操做的時已經被其它線程修改過了,因而便再次進入循環下一次操做,直到成功爲止。
CAS指令在Intel CPU上稱爲CMPXCHG指令, 它的做用是將指定內存地址的內容與所給的某個值相比,若是相等,則將其內容替換爲指令中提供的新值,若是不相等,則更新失敗。這一比較並交換的操做是原子的,不能夠被中斷。初一看,CAS也包含了讀取、比較 (這也是種操做)和寫入這三個操做,和以前的i++並無太大區別,是的,的確在操做上沒有區別,但 CAS是經過硬件命令保證了原子性,而i++沒有,且硬件級別的原子性比i++這樣高級語言的軟件級別的運行速度要快地多。雖然CAS也包含了多個操做,但其的運算是固定的(就是個比較),這樣的鎖定性能開銷很小。
經過查看AtomicInteger的源碼可知,
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
經過申明一個volatile (內存鎖定,同一時刻只有一個線程能夠修改內存值)類型的變量,再加上unsafe.compareAndSwapInt的方法,來保證明現線程同步的。
優勢:AtomicInteger比直接使用傳統的java鎖機制(阻塞的)有什麼好處?最大的好處就是能夠避免多線程的優先級倒置和死鎖狀況的發生,固然高併發下的性能提高也是很重要的。
比較:
摘自:構建更健壯的系統:不一樣的業務放在不一樣的線程/線程池裏面
對於不一樣性質的任務來講,
- CPU密集型任務應配置儘量小的線程,如配置CPU個數+1的線程數,
- IO密集型任務應配置儘量多的線程,由於IO操做不佔用CPU,不要讓CPU閒下來,應加大線程數量,如配置兩倍CPU個數+1,
- 而對於混合型的任務,若是能夠拆分,拆分紅IO密集型和CPU密集型分別處理,前提是二者運行的時間是差很少的,若是處理時間相差很大,則不必拆分了。
線程安全的計數器實現原理簡介:
在java中volatile關鍵字能夠保證共享數據的可見性,它會把更新後的數據從工做內存刷新進共享內存,並使其餘線程中工做內存中的數據失效,進而從主存中讀入最新值來保證共享數據的可見性,實現線程安全的計數器經過循環CAS操做來實現。就是先獲取一箇舊指望值值,再比較獲取的值與主存中的值是否一致,一致的話就更新,不一致的話接着循環,直到成功爲止.
程序參考:java如何實現線程安全的計數器
Java 提供了一組atomic class來幫助咱們簡化同步處理。基本工做原理是使用了同步synchronized的方法實現了對一個long, integer, 對象的增、減、賦值(更新)操做.
程序參考:Java線程安全的計數器 ;