Java線程工具類

01 | Lock和Condition:隱藏在并發(fā)包中的管程?


在并發(fā)編程領域,有兩大核心問題:一個是互斥,即同一時刻只允許一個線程訪問共享資源;另一個是同步,即線程之間如何通信、協(xié)作。

Java SDK 并發(fā)包通過 Lock 和 Condition 兩個接口來實現(xiàn)管程,其中 Lock 用于解決互斥問題,Condition 用于解決同步問題。

Java 語言本身提供的 synchronized 也是管程的一種實現(xiàn),既然 Java 從語言層面已經(jīng)實現(xiàn)了管程了,那為什么還要在 SDK 里提供另外一種實現(xiàn)呢?


再造管程的理由

1、能夠相應中斷。

2、支持超時。

3、非阻塞地獲取鎖。

Lock接口方法

如何保證可見性,代碼如下

lock工具類的使用

它是利用了 volatile 相關的 Happens-Before 規(guī)則。Java SDK 里面的 ReentrantLock,內(nèi)部持有一個 volatile 的成員變量 state,獲取鎖的時候,會讀寫 state 的值;解鎖的時候,也會讀寫 state 的值(簡化后的代碼如下面所示)。也就是說,在執(zhí)行 value+=1 之前,程序先讀寫了一次 volatile 變量 state,在執(zhí)行 value+=1 之后,又讀寫了一次 volatile 變量 state。根據(jù)相關的 Happens-Before 規(guī)則:順序性規(guī)則:對于線程 T1,value+=1 Happens-Before 釋放鎖的操作 unlock();volatile 變量規(guī)則:由于 state = 1 會先讀取 state,所以線程 T1 的 unlock() 操作 Happens-Before 線程 T2 的 lock() 操作;傳遞性規(guī)則:線程 T1 的 value+=1 Happens-Before 線程 T2 的 lock() 操作。



什么是可重入鎖

ReentrantLock,可重入鎖。線程可以重復獲取同一把鎖。

可重入函數(shù),指的是多個線程可以同時調(diào)用該函數(shù),每個線程都能得到正確結果;同時在一個線程內(nèi)支持線程切換,無論被切換多少次,結果都是正確的。


公平鎖與非公平鎖

ReentrantLock構造函數(shù)

用鎖的最佳實踐

永遠只在更新對象的成員變量時加鎖

永遠只在訪問可變的成員變量時加鎖

永遠不在調(diào)用其他對象的方法時加鎖



02 |?Executor與線程池:如何創(chuàng)建正確的線程池?


線程是一個重量級的對象,應該避免頻繁創(chuàng)建和銷毀。

線程池是一種生產(chǎn)者 - 消費者模式

線程池的使用方是生產(chǎn)者,線程池本身是消費者。


如何使用 Java 中的線程池

最核心的是 ThreadPoolExecutor,它強調(diào)的是 Executor,而不是一般意義上的池化資源。

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)


把線程池類比為一個項目組,而線程就是項目組的成員。

corePoolSize:表示線程池保有的最小線程數(shù)。有些項目很閑,但是也不能把人都撤了,至少要留 corePoolSize 個人堅守陣地。

maximumPoolSize:表示線程池創(chuàng)建的最大線程數(shù)。當項目很忙時,就需要加人,但是也不能無限制地加,最多就加到 maximumPoolSize 個人。當項目閑下來時,就要撤人了,最多能撤到corePoolSize 個人。

keepAliveTime & unit:上面提到項目根據(jù)忙閑來增減人員,那在編程世界里,如何定義忙和閑呢?很簡單,一個線程如果在一段時間內(nèi),都沒有執(zhí)行任務,說明很閑,keepAliveTime 和 unit 就是用來定義這個“一段時間”的參數(shù)。也就是說,如果一個線程空閑了keepAliveTime & unit這么久,而且線程池的線程數(shù)大于 corePoolSize ,那么這個空閑的線程就要被回收了。

workQueue:工作隊列,和上面示例代碼的工作隊列同義。

threadFactory:通過這個參數(shù)你可以自定義如何創(chuàng)建線程,例如你可以給線程指定一個有意義的名字。handler:通過這個參數(shù)你可以自定義任務的拒絕策略。如果線程池中所有的線程都在忙碌,并且工作隊列也滿了(前提是工作隊列是有界隊列),那么此時提交任務,線程池就會拒絕接收。至于拒絕的策略,你可以通過 handler 這個參數(shù)來指定。

ThreadPoolExecutor 已經(jīng)提供了以下 4 種策略。CallerRunsPolicy:提交任務的線程自己去執(zhí)行該任務。AbortPolicy:默認的拒絕策略,會 throws RejectedExecutionException。DiscardPolicy:直接丟棄任務,沒有任何異常拋出。DiscardOldestPolicy:丟棄最老的任務,其實就是把最早進入工作隊列的任務丟棄,然后把新任務加入到工作隊列。


使用線程池要注意些什么

考慮到 ThreadPoolExecutor 的構造函數(shù)實在是有些復雜,所以 Java 并發(fā)包里提供了一個線程池的靜態(tài)工廠類 Executors,利用 Executors 你可以快速創(chuàng)建線程池。但是Executors并不建議使用 Executors 的最重要的原因是:Executors 提供的很多方法默認使用的都是無界的 LinkedBlockingQueue,高負載情境下,無界隊列很容易導致 OOM,而 OOM 會導致所有請求都無法處理,這是致命問題。所以強烈建議使用有界隊列。


03 | Future獲取任務執(zhí)行結果


Future獲取任務執(zhí)行結果。(ThreadPoolExecutor 繼承AbstractExecutorService抽象類,AbstractExecutorService類中有submit。)

Java 通過 ThreadPoolExecutor 提供的 3 個 submit() 方法和 1 個 FutureTask 工具類來支持獲得任務執(zhí)行結果的需求。這3 個 submit() 方法的方法簽名如下:

submit方法簽名

提交 Runnable 任務 submit(Runnable task) :這個方法的參數(shù)是一個 Runnable 接口,Runnable 接口的 run() 方法是沒有返回值的,所以 submit(Runnable task) 這個方法返回的 Future 僅可以用來斷言任務已經(jīng)結束了,類似于 Thread.join()。

提交 Callable 任務 submit(Callable?task):這個方法的參數(shù)是一個 Callable 接口,它只有一個 call() 方法,并且這個方法是有返回值的,所以這個方法返回的 Future 對象可以通過調(diào)用其 get() 方法來獲取任務的執(zhí)行結果。

提交 Runnable 任務及結果引用 submit(Runnable task, T result):這個方法很有意思,假設這個方法返回的 Future 對象是 f,f.get() 的返回值就是傳給 submit() 方法的參數(shù) result。需要你注意的是 Runnable 接口的實現(xiàn)類 Task 聲明了一個有參構造函數(shù) Task(Result r) ,創(chuàng)建 Task 對象的時候傳入了 result 對象,這樣就能在類 Task 的 run() 方法中對 result 進行各種操作了。result 相當于主線程和子線程之間的橋梁,通過它主子線程可以共享數(shù)據(jù)。

Future 接口有 5 個方法,我都列在下面了,它們分別是取消任務的方法 cancel()、判斷任務是否已取消的方法 isCancelled()、判斷任務是否已結束的方法 isDone()以及2 個獲得任務執(zhí)行結果的 get() 和 get(timeout, unit),其中最后一個 get(timeout, unit) 支持超時機制。通過 Future 接口的這 5 個方法你會發(fā)現(xiàn),我們提交的任務不但能夠獲取任務執(zhí)行結果,還可以取消任務。不過需要注意的是:這兩個 get() 方法都是阻塞式的,如果被調(diào)用的時候,任務還沒有執(zhí)行完,那么調(diào)用 get() 方法的線程會阻塞,直到任務執(zhí)行完才會被喚醒。


FutureTask 實現(xiàn)了 Runnable 和 Future 接口

由于實現(xiàn)了 Runnable 接口,所以可以將 FutureTask 對象作為任務提交給 ThreadPoolExecutor 去執(zhí)行,也可以直接被 Thread 執(zhí)行;又因為實現(xiàn)了 Future 接口,所以也能用來獲得任務的執(zhí)行結果。


總結如下:Executor(ThreadPoolExecutor核心類)就是Runnable和Callable的調(diào)度容器,F(xiàn)uture(FutureTask?)就是對于具體的Runnable或者Callable任務的執(zhí)行結果進行取消、查詢是否完成、獲取結果、設置結果操作。

線程相關類

推薦CSDN優(yōu)質(zhì)線程池博客:Java線程池-ThreadPoolExecutor原理分析與實戰(zhàn)


04 |?Fork/Join:單機版的MapReduce

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容