是AbstractQueuedSynchronizer的簡稱,JUC的核心node
底層是sync queue雙向鏈表,還可能有condition queue單向鏈表,使用Node實現FIFO隊列,能夠用於構建同步隊列或者其餘同步裝置的基礎程序員
框架數組
利用了int類型表示狀態,在AQS中有個state的成員變量,基於AQS的ReentrantLock,state表示獲取鎖的線程數,等於0,沒有,1有,緩存
大於1表示重入鎖的數量。服務器
protected final int getState() { //獲取當前同步狀態 return this.state; } protected final void setState(int var1) { //設置當前同步狀態 this.state = var1; } //使用CAS設置當前狀態,該方法可以保證狀態設置的原子性 protected final boolean compareAndSetState(int var1, int var2) { return unsafe.compareAndSwapInt(this, stateOffset, var1, var2); }
基於模板方法,須要繼承AQS,重寫某些方法多線程
能夠實現排它鎖和共享鎖的模式(獨佔Reentrantlock、共享countdownlatch,同時實現一種)併發
一、實現思路:框架
首先AQS中維護了一個queue來管理鎖,線程會嘗試獲取鎖,若是失敗,就將當前線程以及等待狀態等信息封裝成一個node節點,加入到syncjvm
queue的tail,head node的線程釋放鎖的時候,會喚醒隊列中的後繼線程,然後續節點在獲取鎖成功的時候把本身設置爲首節點工具
就是由於這些設計,jdk有不少基於AQS的設計,一些經常使用的組件:
countdownlatch、semaphore、CyclicBarrier、Reentrantlock、Condition、Futuretask等
二、同步組件:
2.1).CountDownLatch(閉鎖):
能夠實現阻塞當前的線程,經過一個計數器進行初始化,這個計數器都是進行原子操做,只能同時有個線程操做這個計數器,調用CountDown
Latch的await()會處於阻塞狀態,其餘線程調用Countdown(),每次減一,直到計數器變成零
這時候全部由於調用await()阻塞的線程才能繼續往下執行,CountDownLatch只能執行一次,不能重置,想要使用重置的計數器,可使用
2.2).CyclicBarrier
await()須要等到countdown()將計數器減到0,纔會執行後續的代碼。await()能夠有時間參數,選擇等待多長時間事後就會執行await後續的
代碼。
countDown()儘可能卸載finally內部
countDownLatch.await();
countDownLatch.await(10, TimeUnit.MILLISECONDS);
使用場景:
2.3).Semaphore:信號量
控制併發訪問的個數,用於只能提供有限訪問的資源
semaphore.acquire(3); // 獲取多個許可 test(threadNum); semaphore.release(3); // 釋放多個許可 Semaphore semaphore = new Semaphore(2);容許線程數必定要大於等於acquire和release的個數 semaphore.tryAcquire()嘗試獲取許可,沒有獲取許可的線程都會丟棄 semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)在5000ms中嘗試獲取許可
2.4).CyclicBarrier
用於多線程計算數據,最後合併計算結果,例如Excel不少頁流水,經過多線程計算每一頁流水,最後計算總的
經過調用await()方法,線程進入等待狀態,計數器進行加一操做,當值等於設置的初始值時,全部阻塞的線程繼續執行
private static CyclicBarrier barrier = new CyclicBarrier(5, () -> { log.info("callback is running"); });
經過使用lambda,當計數器知足條件優先執行lambda表達式裏面的代碼
2.5).CyclicBarrier和CountDownLatch區別:
一、CountDownLatch只能使用一次,而CyclicBarrier能夠循環利用,使用reset進行重置
二、CountDownLatch描述:1或N個線程須要等待其餘線程完成某個操做,才能繼續往下執行
三、CyclicBarrier:多個線程之間相互等待,知道全部線程都知足某個條件才能繼續執行後續操做,是各個線程直接相互等待的操做
countdown表現: CountDownLatch表現: 1 is ready 1 is ready 2 is ready 2 is ready 3 is ready 3 is ready 1 continue 1 continue 2 continue 2 continue 3 continue 3 continue 4 is ready 4 is ready 4 continue 5 is ready 5 is ready 6 is ready 5 continue 4 continue 6 is ready 5 continue 6 continue 6 continue
一、ReadWriteLock:
二、ReentrantReadWriteLock:
支持多線程讀,若是有一個線程已經佔用了讀鎖,則此時其餘線程若是要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。
若是有一個線程已經佔用了寫鎖,則此時其餘線程若是申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。
一個是讀操做相關的鎖,稱爲共享鎖;一個是寫相關的鎖,稱爲排他鎖
一個線程要想同時持有寫鎖和讀鎖,必須先獲取寫鎖再獲取讀鎖;寫鎖能夠「降級」爲讀鎖;讀鎖不能「升級」爲寫鎖。
讀讀共享、其餘都是互斥
三、ReentrantLock:
注意不要把lock的實例化作成局部變量,每一個線程執行該方法時都會保存一個副本,那麼理所固然每一個線程執行到lock.lock()處獲取的是不一樣
的鎖,這樣lock就不能起做用了。
一、synchronized:
可重入性,jvm實現,在以前和ReentrantLock性能差異很大,可是引入了偏向鎖、輕量級鎖,效率已經相差不大,只能使用非公平鎖,
能夠經過一些工具進行監控,jvm自動作加鎖、解鎖操做
二、ReentrantLock:
可重入性,jdk實現,粒度更小,能夠指定公平鎖(先等待的線程先得到鎖)或非公平鎖,提供一個condition類,
能夠實現分組喚醒須要喚醒的線程,而synchronized關鍵字要麼喚醒一個線程,要麼所有線程,能夠經過lockInterruptibly()中斷等待鎖的線程
機制,必定要記得在finally釋放鎖
四、StampLock:
對吞吐量有很大的改進,性能上有很大的提高,特別是適合讀操做比較多的狀況
ReentrantLock、ReentrantReadWriteLock、StampLock等lock都是對象層面的鎖定
五、鎖使用原則:
一、當只有少許線程競爭的時候,可使用synchronized,並且不會引起死鎖
二、線程競爭很多,線程增加可以預估,能夠選擇ReentrantLock
synchronized和ReentrantLock都是可重入鎖
鎖的分配機制是基於線程的分配,而不是基於方法的分配,在method1中已經獲取了對象鎖,在方法內部調用method2不用從新獲取鎖。
synchronized就不是可中斷鎖,而Lock是可中斷鎖。
lockInterruptibly()的用法時已經體現了Lock的可中斷性。
公平鎖是指當一個鎖被釋放的時候,等待時間最長的線程會獲取該鎖,非公平鎖可能致使某些線程永遠不會獲取到鎖
synchronized不是公平鎖,ReentrantLock和ReentrantReadWriteLock,它默認狀況下是非公平鎖,可是能夠設置爲公平鎖。
ReentrantLock在實例化的時候參數true表示公平鎖,false表示非公平鎖,並且有不少判斷鎖狀態的方法。
多線程讀操做不會發生衝突
condition:await()、signal()能夠實現多路通知功能,可是通知部分線程要使用多個condition類,不然會所有喚醒
Callable與Runnable、Thread接口對比:
Future接口:
能夠獲得線程任務方法的返回值
FutureTask類:
實現了Runnable、Future,使用場景:線程A作一件事,線程B作別的事,在須要的時候能夠的到線程A的返回值
Fork/Join(jdk1.7):
就是把大任務拆分紅若干小任務,放到雙端隊列,每一個隊列分配一個線程,先作完的線程幫助其餘線程,一個從下面,一個從上面,並行執行
,最終彙總結果,可是某些狀況下仍是有線程競爭的狀況
侷限性:
一、只能經過fork、join進行操做 二、不能有io操做 三、任務不能拋出檢查異常
除了優先級隊列和LIFO隊列外,隊列都是以FIFO(先進先出)的方式對各個元素進行排序的
add(E e):
將元素e插入到隊列末尾,若是插入成功,則返回true;若是插入失敗(即隊列已滿),則會拋出異常
remove():
移除隊首元素,若移除成功,則返回true;若是移除失敗(隊列爲空),則會拋出異常
offer(E e):
將元素e插入到隊列末尾,若是插入成功,則返回true;若是插入失敗(即隊列已滿),則返回false
poll():
移除並獲取隊首元素,若成功,則返回隊首元素;不然返回null
peek():
獲取隊首元素,若成功,則返回隊首元素;不然返回null
注意點:
一、對於非阻塞隊列,通常狀況下建議使用offer、poll和peek三個方法,不建議使用add和remove方法。由於使用offer、poll和peek三個方法
可以經過返回值
二、判斷操做成功與否,而使用add和remove方法卻不能達到這樣的效果。注意,非阻塞隊列中的方法都沒有進行同步措施。
三、阻塞隊列對於上面五個方法有作同步處理,而非阻塞隊列沒有同步
put(E e) take() offer(E e,long timeout, TimeUnit unit) poll(long timeout, TimeUnit unit) put方法用來向隊尾存入元素,若是隊列滿,則等待; take方法用來從隊首取元素,若是隊列爲空,則等待; offer方法用來向隊尾存入元素,若是隊列滿,則等待必定的時間,當時間期限達到時,若是尚未插入成功,則返回false;不然返回true; poll方法用來從隊首取元素,若是隊列空,則等待必定的時間,當時間期限達到時,若是取到,則返回null;不然返回取得的元素;
一、BlockingQueue:
主要用在生產者消費者場景,不須要關注何時阻塞和喚醒
二、ArrayBlockingQueue:
有界的阻塞隊列,就是容量是有限的,初始化指定容量大小,FIFO,內部是由數組實現
三、DelayQueue:
必須實現Delay接口,它的元素要進行排序,應用場景:定時關閉鏈接、緩存對象,超時處理等
四、LinkedBlockingQueue:
內部是鏈表,和ArrayBlockingQueue類似,FIFO
五、priorityBlockingQueue:
容許插入null
六、SynchronousQueue:
只能插入一個值,插入一個元素就會阻塞,也叫同步隊列
優勢:
一、減小了建立和銷燬線程的次數,每一個工做線程均可以被重複利用,可執行多個任務
二、能夠根據系統的承受能力,調整線程池中工做線程的數據,防止由於消耗過多的內存致使服務器崩潰
ThreadPoolExecutor:
注意corePoolSize、maximumPoolSize、workQueue等參數之間的關係,詳情見9-1
參數:
一、corePoolSize
核心池的大小。在建立了線程池以後,默認狀況下,線程池中沒有任何線程,而是等待有任務到來才建立線程去執行任務。默認狀況下,在建立了線程池以後,
線程池中的線程數爲0,當有任務到來後就會建立一個線程去執行任務
二、maximumPoolSize
池中容許的最大線程數,這個參數表示了線程池中最多能建立的線程數量,當任務數量比corePoolSize大時,任務添加到workQueue,當workQueue滿了,
將繼續建立線程以處理任務,maximumPoolSize表示的就是wordQueue滿了,線程池中最多能夠建立的線程數量
三、keepAliveTime
只有當線程池中的線程數大於corePoolSize時,這個參數纔會起做用。當線程數大於corePoolSize時,終止前多餘的空閒線程等待新任務的最長時間
四、unit
keepAliveTime時間單位
五、workQueue
存儲還沒來得及執行的任務
六、threadFactory
執行程序建立新線程時使用的工廠
七、handler
因爲超出線程範圍和隊列容量而使執行被阻塞時所使用的處理程序
解釋:
一、池中線程數小於corePoolSize,新任務都不排隊而是直接添加新線程
二、池中線程數大於等於corePoolSize,workQueue未滿,首選將新任務加入workQueue而不是添加新線程
三、池中線程數大於等於corePoolSize,workQueue已滿,可是線程數小於maximumPoolSize,添加新的線程來處理被添加的任務
四、池中線程數大於大於corePoolSize,workQueue已滿,而且線程數大於等於maximumPoolSize,新任務被拒絕,使用handler處理被拒絕的任務
強烈建議程序員使用較爲方便的Executors工廠方法Executors.newCachedThreadPool()(無界線程池,能夠進行線程自動回收)、
Executors.newFixedThreadPool(int)(固定大小線程池)和Executors.newSingleThreadExecutor()(單個後臺線程),它們均爲大多數使用場景預
定義了設置。因此重點關注一下JDK推薦的Executors
併發最佳實踐:
一、使用本地變量
二、使用不可變類
三、最小化鎖的做用範圍:S=1/(1-a+a/n)
四、寧肯使用同步也不使用線程的wait和notify
五、使用BlockingQueue實現生產-消費模式
六、使用併發集合而不是加了鎖的同步集合
七、使用Semaphore建立有界的訪問
八、在使用synchronized時,寧肯使用同步代碼塊,也不使用同步方法
九、避免使用靜態變量