線程池 & JMM 內存模型
文章目錄
- 線程池 & JMM 內存模型
- 線程池
- 線程池的創建
- ThreadPoolExecutor 七大參數
- 飽和策略
- ExecutorService 提交線程任務對象執行的方法:
- ExecutorService 關閉線程池的方法:
- 線程池最大線程數如何確定?
- volatile - 多線程之間共享變量的可見性
- JMM 內存模型
- 八種操作
- JMM 對這八種指令的使用,制定了如下規則:
線程池
使用線程池可以實現線程的復用,減少頻繁創建和銷毀線程對象造成的資源消耗。
好處:
-
降低資源消耗:減少了創建和銷毀線程的次數;
-
提高響應速度:不需要頻繁的創建線程;
-
提高線程的可管理性:線程池可以約束系統中最多的線程數;
線程池的創建
java.util.concurrent.Executors
提供了一系列靜態方法創建線程池對象,線程池在 Java 中表現為 ExecutorService
接口。
-
使用
ThreadPoolExecutor
構造方法; -
使用
Executor
框架的?具類Executors
:實際也是調用了ThreadPoolExecutor
構造方法,阿里巴巴開發手冊強烈不允許使用該種方式。
FixedThreadPool
: 該方法返回?個固定線程數量的線程池。該線程池中的線程數量始終不變。當有?個新的任務提交時,線程池中若有空閑線程,則立即執行。若沒有,則新的任務會被暫存在?個任務隊列中,待有線程空閑時,便處理在任務隊列中的任務。SingleThreadExecutor
: 方法返回?個只有?個線程的線程池。若多余?個任務被提交到該線程池,任務會被保存在?個任務隊列中,待線程空閑,按先入先出的順序執?隊列中的任務。CachedThreadPool
: 該方法返回?個可根據實際情況調整線程數量的線程池。線程池的線程數量不確定,但若有空閑線程可以復?,則會優先使用可復?的線程。若所有線程均在?作,又有新的任務提交,則會創建新的線程處理任務。所有線程在當前任務執行完畢后,將返回線程池進行復?。
推薦創建線程池方式:ThreadPoolExecutor
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, // 核心線程數5, // 最大線程數,超出阻塞隊列時才會觸發最大線程數3, // 超出核心線程數時,終止空閑線程的等待時間TimeUnit.SECONDS, // 時間單位new LinkedBlockingQueue<Runnable>(3), // 阻塞隊列Executors.defaultThreadFactory(), // 默認創建線程工廠new ThreadPoolExecutor.AbortPolicy() // 拒絕策略
);
ThreadPoolExecutor 七大參數
-
corePoolSize
:核心線程數,保留在線程池中的線程數,即使它們處于空閑狀態,除非設置了allowCoreThreadTimeOut
; -
maximumPoolSize
:最大線程數,當工作隊列中存放的任務達到隊列容量的時候,當前可以同時運行的線程數量變為最?線程數; -
keepAliveTime
:當線程數大于核心線程數時,這是多余空閑
線程在終止前等待新任務的最長時間; -
unit:keepAliveTime
參數的時間單位; -
workQueue
:工作隊列,超出核心線程數的任務會保存在任務隊列, 這個隊列將只保存 execute 方法提交的 Runnable任務; -
threadFactory
:執行程序創建新線程時使用的工廠; -
handler
:飽和策略(拒絕策略),執行被阻塞時使用的處理程序,因為達到了線程邊界和隊列容量;
飽和策略
-
ThreadPoolExecutor.AbortPolicy
:拋出RejectedExecutionException
來拒絕新任務的處理。 -
ThreadPoolExecutor.CallerRunsPolicy
:調用執行自己的線程運行任務(使用提交該任務的線程執行)。您不會任務請求。但是這種策略會降低對于新任務提交速度,影響程序的整體性能。另外,這個策略喜歡增加隊列容量。如果您的應?程序可以承受此延遲并且你不能任務丟棄任何?個任務請求的話,你可以選擇這個策略。 -
ThreadPoolExecutor.DiscardPolicy
: 不處理新任務,直接丟棄掉。 -
ThreadPoolExecutor.DiscardOldestPolicy
: 此策略將丟棄最早的未處理的任務請求。
ExecutorService 提交線程任務對象執行的方法:
-
Future<?> submit(Runnable task):提交一個 Runnable 的任務對象給線程池執行;
-
Future<?> submit(Callable task):提交一個 Callable 的任務對象給線程池執行,可以通過 get() 得到返回結果。
-
public void execute(Runnable command):不返回結果使用 execute;
ExecutorService 關閉線程池的方法:
-
shutdown()
:等待任務執行完畢以后才會關閉線程池; -
shutdownNow()
:立即關閉線程池的代碼,無論任務是否執行完畢,相當于給每個線程調用了 intercept() 方法。
線程池提交線程任務對象會自動啟動線程執行。
ExecutorService pools = Executors.newFixedThreadPool(3);// 實現 Callable 接口
Future<String> result= pools.submit((Callable<String>) () -> {});
// 獲取線程執行返回的結果
String r = result.get()// 實現 Runnable 接口
pools.submit(() -> {});
線程池最大線程數如何確定?
-
CPU 密集型:定義為機器 CPU 核數,
Runtime.getRuntime().availableProcessors()
-
IO 密集型:定義為 IO 密集線程的 2 倍;
線程池主要執行流程
volatile - 多線程之間共享變量的可見性
引入:A 線程修改了共享變量的值,但是在主線程中讀取到的還是之前的值,修改后的值無法讀取到。
解釋:根據 JMM 內存模型,線程在本地內存中會有共享變量的副本,A 線程修改了其本地內存的變量更新到主內存中,當主線程操作共享變量時,還是讀取的主線程中本地內存的共享變量副本,此時還沒有跟主內存共享變量同步,導致無法讀取到修改后的值,這個現象稱為線程之間共享變量的不可見性。
解決方案:
- 加鎖:
? a. 線程獲得鎖;
? b. 清空本地工作內存;
? c. 從主內存中拷貝最新值到工作內存;
- 使用 volatile 關鍵字修飾共享變量:使用 volatile 關鍵字修飾的變量被修改后,主內存更新后會將其他線程本地工作內存中的變量副本失效,重新讀取主內存的最新值,從而保證了可見性。
區別:volatile 關鍵字不保證原子性。
volatile 是 Java 虛擬機提供輕量級的同步機制。
特點:
-
保證可見性
-
不保證原子性
-
禁止指令重排
JMM 內存模型
JMM
(Java Memory Model),Java 內存模型。是 Java 虛擬機規范中所定義的一種內存模型。 Java 內存模型(Java Memory Model)描述了 Java 程序中各種變量(線程共享變量)的訪問規則,以及在 JVM 中將變量存儲到內存和從內存中讀取變量這樣的底層細節。 所有的共享變量都存儲于主內存。這里所說的變量指的是實例變量和類變量。不包含局部變量,因為局部變量是線程私有的,因此不存在競爭問題。每一個線程還存在自己的工作內存,線程的工作內存,保留了被線程使用的變量的工作副本。線程對變量的所有的操作(讀,取)都必須在工作內存中完成,而不能直接讀寫主內存中的變量,不同線程之間也不能直接訪問對方工作內存中的變量,線程間變量的值的傳遞需要通過主內存完成。
八種操作
內存交互操作有 8 種,虛擬機實現必須保證每一個操作都是原子的,不可在分的(對于double和long類型的變量來說,load、store、read 和 write 操作在某些平臺上允許例外)
-
lock (鎖定):作用于主內存的變量,把一個變量標識為線程獨占狀態;
-
unlock (解鎖):作用于主內存的變量,它把一個處于鎖定狀態的變量釋放出來,釋放后的變量才可以被其他線程鎖定;
-
read (讀取):作用于主內存變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨后的 load動作使用;
-
load (載入):作用于工作內存的變量,它把 read 操作從主存中變量放入工作內存中;
-
use (使用):作用于工作內存中的變量,它把工作內存中的變量傳輸給執行引擎,每當虛擬機遇到一個需要使用到變量的值,就會使用到這個指令;
-
assign (賦值):作用于工作內存中的變量,它把一個從執行引擎中接受到的值放入工作內存的變量副本中
-
store (存儲):作用于主內存中的變量,它把一個從工作內存中一個變量的值傳送到主內存中,以便后續的 write 使用
-
write (寫入):作用于主內存中的變量,它把 store 操作從工作內存中得到的變量的值放入主內存的變量中
JMM 對這八種指令的使用,制定了如下規則:
-
不允許 read 和 load、store 和 write 操作之一單獨出現。即使用了read 必須 load,使用了store 必須 write
-
不允許線程丟棄他最近的 assign 操作,即工作變量的數據改變了之后,必須告知主存
-
不允許一個線程將沒有 assign 的數據從工作內存同步回主內存
-
一個新的變量必須在主內存中誕生,不允許工作內存直接使用一個未被初始化的變量。就是懟變量實施 use、store 操作之前,必須經過 assign 和 load 操作
-
一個變量同一時間只有一個線程能對其進行 lock。多次 lock 后,必須執行相同次數的 unlock 才能解鎖
-
如果對一個變量進行 lock 操作,會清空所有工作內存中此變量的值,在執行引擎使用這個變量前,必須重新load 或 assign 操作初始化變量的值
-
如果一個變量沒有被 lock,就不能對其進行 unlock 操作。也不能 unlock 一個被其他線程鎖住的變量
-
對一個變量進行 unlock 操作之前,必須把此變量同步回主內存