目錄
synchronized
class TestSynchronized{
|
類別 |
Synchronized |
Lock |
存在形式 |
JAVA關鍵字,JVM |
是一個類 |
鎖的釋放 |
|
在finally中必須釋放鎖,不然會造成死鎖 |
獲取鎖 |
如果A線程獲取了鎖,那麼B線程等待。如果A線程阻塞,那麼B線程會一直等待 |
Lock有多種鎖的獲取方式,都可以嘗試獲得鎖,線程可以不用一直等待 |
鎖狀態 |
無法判斷 |
可判斷 |
鎖類型 |
悲觀鎖(可重入 不可中斷 非公平) |
樂觀鎖(可重入 可判斷 可公平(兩者皆可)) |
性能 |
少量同步 |
大量同步 |
|
偏向鎖-》輕量級鎖-》重量級鎖(1.6後) |
互斥鎖ReentrantLock |
Volatile
volatile:用來修飾被不同線程訪問和修改的變量
volatile關鍵字與JVM內存模型相關
Java語言是支持多線程的,爲了解決線程併發的問題,在語言內部引入了同步塊和volatile關鍵字機制
volatile關鍵字機制:
synchronized修飾方法和代碼塊,以實現同步
用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改後的值。volatile經常被誤用進行原子性的操作。但是這些操作並不是真正的原子性。在讀取加載之後,如果變量發生了變化,線程工作內存中的值由於已加載,不會產生對應的變法。對於volatile修改的變量,JVM只是保證從內存加載到線程工作內存的值是最新的。
交互圖:
public class VolatileTest { |
信號量Semaphore
在多線程中,線程間傳遞信號的一種方式。
與互斥量的區別
- 互斥量用於線程的互斥,信號量用於線程的同步
- 互斥量值只能爲0/1,信號量值可以爲非負整數。信號量可以實現多個同類資源的多線程互斥和同步。
- 互斥量的加鎖和解鎖必須由同一線程分別對應使用,信號量可以由一個線程釋放,另一個線程得到。
class TestSemaphore{ |
實現所有線程在等待某個事件的發生纔去執行
1.閉鎖CountDownLatch
閉鎖是典型的等待事件發生的同步工具類,將閉鎖的初始值設置1,所有線程調用await方法等待,當事件發生時調用countDown將閉鎖值減爲0,則所有await等待閉鎖的線程得以繼續執行。
class TestCountDownLatch{ |
2.阻塞隊列BlockingQueue
所有等待事件的線程嘗試從空的阻塞隊列獲取元素,將阻塞,當事件發生時,向阻塞隊列中同時放入N個元素(N的值與等待的線程數相同),則所有等待的線程從阻塞隊列中取出元素後得以繼續執行。
3.信號量Semaphore
設置信號量的初始值爲等待的線程數N,一開始將信號量申請完,讓剩餘的信號量爲0,待事件發生時,同時釋放N個佔用的信號量,則等待信號量的所有線程將獲取信號量得以繼續執行。
class TestSemaphore{ |
4.柵欄CyclicBarrier
設置柵欄的初始值爲1,當事件發生時,調用barrier.wait()衝破設置的柵欄,將調用指定的Runable線程執行,在該線程中啓動N個新的子線程執行。這個方法並不是讓執行中的線程全部等待在某個點,待某一事件發生後繼續執行。
class TestCyclicBarrier{ |
CAS缺陷以及如何解決
參考地址:https://blog.csdn.net/jeffleo/article/details/75269618
CAS:Compare And Swap,即比較並交換。(AtomicInteger)
實現了Java多線程的併發操作。整個AQS同步組件、Atomic原子類操作等都是以CAS實現的,甚至ConcurrentHashMap在1.8的版本中也調整爲了CAS+Synchronized。可以說CAS是整個JUC的基石。
缺陷:
主要表現在三個方法:
循環時間太長
只能保證一個共享變量原子操作
ABA問題:解決方案則是版本號
AQS
參考資料:https://www.cnblogs.com/daydaynobug/p/6752837.html
AQS:AbstractQueuedSynchronized, 抽象的隊列式的同步器,AQS定義了一套多線程訪問共享資源的同步器框架,許多同步類實現都依賴於它,如常用的ReentrantLock/Semaphore/CountDownLatch
AQS裏的state只有兩種狀態:0表示未鎖定,1表示鎖定
死鎖、活鎖
死鎖:指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。4個產生條件:
- 互斥條件
- 請求和保持條件
- 不剝奪條件
- 環路等待條件
活鎖:指的是任務或者執行者沒有被阻塞,由於某些條件沒有滿足,導致一直重複嘗試—失敗—嘗試—失敗的過程。處於活鎖的實體是在不斷的改變狀態,活鎖有可能自行解開。
線程池
資源地址:https://blog.csdn.net/wxq544483342/article/details/53118674
線程池的種類
1、創建一個緩衝池,緩衝池容量大小爲Integer.MAX_VALUE
ExecutorService pool = Executors.newCachedThreadPool();
2、創建容量爲1的緩衝池
ExecutorService pool = Executors.newSingleThreadExecutor();
3、創建固定容量大小的緩衝池
ExecutorService pool = Executors.newFixedThreadPool(int);
4、創建一個定長線程池,支持定時及週期性任務執行。
ExecutorService pool = Executors.newScheduledThreadPool(1);
5、創建一個擁有多個任務隊列(以便減少連接數)的線程池。
ExecutorService pool = Executors.newWorkStealingPool();
ExecutorService類的方法:
void shutdown();
關閉線程池 不可以再 submit 新的 task,已經 submit 的將繼續執行
List<Runnable> shutdownNow();
關閉線程池 試圖停止當前正在執行的 task,並返回尚未執行的 task 的 list
boolean isShutdown();
是否已經關閉了線程池,當調用shutdown()或shutdownNow()方法後返回爲true。
boolean isTerminated(); 當調用shutdown()方法後,並且所有提交的任務完成後返回爲true; 當調用shutdownNow()方法後,成功停止後返回爲true; boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; <T> Future<T> submit(Callable<T> task); <T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; |
配置線程池大小
- CPU密集型任務,就需要儘量壓榨CPU,參考值可以設爲 NCPU+1
- IO密集型任務,參考值可以設置爲2*NCPU
工作機制及原理
線程池的兩個核心隊列:
- 線程等待池,即線程隊列BlockingQueue
- 任務處理池(PoolWorker),即正在工作的Thread列表(HashSet)
線程池的核心參數:
- 核心池大小(corePoolSize),即固定大小,設定好之後,線程池的穩定峯值,達到這個值之後池的線程數大小不會釋放。
- 最大處理線程池數(maximumPoolSize),當線程池裏面的線程數超過corePoolSize,小於maximumPoolSize時會動態創建與回收線池裏面的線程池資源。
ThreadPoolExecutor類
構造方法:
/** * * @param corePoolSize 核心池大小 默認情況下,在創建好線程池之後,線程池中的線程數爲0,當有任務來之後,就會創建一個線程去執行任務, * 當線程池中的線程數量達到corePoolSize後,就會把這些任務放到緩存隊列中 * @param maximumPoolSize 線程池最大線程數量,表示在線程池中最多能創建線程的數量;在corePoolSize和maximumPoolSize的線程數會被自動釋放,而小於corePoolSize的則不會。 * @param keepAliveTime 表示線程沒有執行任務時最多保持多久時間會終止。 * 默認情況下,只有當線程池中的線程數大於corePoolSize時,keepAliveTime纔會生效,直到線程池數量不大於corePoolSize, * 即只有當線程池數量大於corePoolSize數量,超出這個數量的線程一旦到達keepAliveTime就會終止。 * 但是如果調用了allowCoreThreadTimeout(boolean)方法,即使線程池的線程數量不大於corePoolSize,線程也會在keepAliveTime之後就終止,知道線程池的數量爲0爲止。 * @param unit 參數keepAliveTime的時間單位,一個時間單位枚舉類。 Nanos/Micros/Millis/Seconds/Minutes/Hours/Days * @param workQueue 一個阻塞隊列,用來存儲等待執行任務的隊列,這個參數選擇也很重要,會對線程池的運行過程產生重大影響。 * 一般來說,這裏的阻塞隊列就是(ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue)。 * @param threadFactory 線程工廠,主要用來創建線程;可以是一個自定義的線程工廠,默認就是Executors.defaultThreadFactory()。用來在線程池中創建線程。 * @param handler 表示當拒絕處理任務時的策略,也是可以自定義的,默認是我們前面的4種取值: * ThreadPoolExecutor.AbortPolicy(默認的,一言不合即拋異常的) * ThreadPoolExecutor.DiscardPolicy(一言不合就丟棄任務) * ThreadPoolExecutor.DiscardOldestPolicy(一言不合就把最近的任務給拋棄,然後執行當前任務) * ThreadPoolExecutor.CallerRunsPolicy(由調用者所在線程來執行任務) */ public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue, public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; } |
ThreadLoad原理
class TestThreadLocal{
|
LockSupport工具
LockSupport提供了park和unpark方法實現阻塞和解除阻塞,都是基於“許可(permit)”作爲關聯。permit相當於一個信號量(0,1),默認是0。
使用偏移量來獲取對象。是因爲線程已經被阻塞了,如果不通過內存的方式,直接調用線程內的方法,線程是不會響應的。
park()、unpark()和wait()、notify()的區別
- 面向的主體不同。LockSupport是針對Thread進行阻塞,可以指定阻塞的對象,每次可以喚醒指定具體的線程。Wait()是以對象,阻塞當前的線程和喚醒單個線程或者所有線程。
- 實現的機制不同。LockSupport可以指定monitor的object對象,但和object.wait()兩者的阻塞隊列並不交叉。
static void testLockSupport(){ |
Condition原理
在經典的生產者和消費者模式中,可以使用Object.wait()和Object. Notify()阻塞和喚醒線程,但這樣處理只有一個等待隊列。在可重入鎖ReentrantLock中,使用AQS的condition可以實現設置多個等待隊列,可以使用lock.newCondition生成一個等待隊列。
static void testCondition() throws InterruptedException { |
Fork/Join框架的理解
並行流:把一個內容分成多個數據塊,並用不同的線程分別處理每個數據塊的流。(fork/join框架)
串行流:則相反
Fork/Join框架:
在必要的情況下,將一個大的任務進行拆分(fork)成若干個子任務(拆到臨界值),再將一個個任務的結果進行join彙總。
Fork/Join與線程池的區別:
Fork/Join採用“工作竊取模式”,當執行新的任務時可以將其拆分成更小的任務執行,並將小任務加到線程隊列中。然後再隨機從一個線程中偷一個任務並把他加入到自己的隊列中。
例如:CPU上有兩個線程A/B,A已經執行完了,B還有任務未執行。這時A將B隊尾的任務偷過來,加入到自己的隊列中。相對於傳統的線程池,Fork/Join更有效的使用了CPU資源。
// ForkJoin這個框架的實現需要繼承RecursiveTask 或者 RecursiveAction, |
分段鎖(JUC)的原理、鎖力度的減小思考
容器中有多把鎖,每一把鎖用於鎖容器中的一部分數據。當多線程時訪問容器中的不同數據段的數據時,線程間就不會發生鎖競爭了。因而有效的提高了高併發的訪問效率。例如ConcurrentHashMap使用的鎖分段技術,首先將數據分成一段一段的存儲,然後給每一段數據一把鎖,當一個線程佔用鎖訪問其中一個段數據時,其他段的數據也可被其他線程訪問。ConcurrentHashMap中使用了一個包含16個鎖的數組,每個鎖保護所在的散列桶的1/16,其中第N個散列桶由第(N mod 16)個鎖來保護。合理的使用散列算法來使關鍵字均勻分佈,那麼可使對鎖的請求減少到原來的1/16。這樣可使ConcurrentHashMap的並發達到16個線程。
獨佔鎖:只有一把鎖,每次只能一個線程能訪問。例如HashTable。採用串行方式,會降低其伸縮性。一般有三種方式降低鎖的競爭:
- 減少鎖的持有時間
- 降低鎖的請求頻率
- 使用帶有協調機制的獨佔鎖,這些機制允許更高的併發性。
八種阻塞隊列以及各個阻塞隊列的特性
public class TestQueue { |