1. 程式人生 > >Java多執行緒程式設計基礎知識彙總

Java多執行緒程式設計基礎知識彙總

## 多執行緒簡介 ### 多工   現代作業系統(Windows、Linux、MacOS)都可以執行多工,多工就是同時執行多個任務。例如在我們的計算機上,一般都同時跑著多個程式,例如瀏覽器,視訊播放器,音樂播放器,Word辦公軟體等等,由於CPU執行程式碼都是一條一條順序執行的,即時是單核CPU也可以同時執行多個任務,作業系統執行多個任務實際上就是輪流讓多個任務交替執行。即使是多核CPU,因為通常任務的數量是遠多於CPU的核數,所以任務也是交替執行的。 ### 程序(Process)   在計算機中,我們把一個任務稱為一個程序。瀏覽器就是一個程序,視訊播放器是另外一個程序,音樂播放器也是一個程序,在某些程序內部還需要同時執行多個子任務。例如我們在使用Word時,在打字的同時需要進行拼寫檢查,還可以在後臺進行列印,我們把子任務稱為執行緒。 程序和執行緒的關係: * 一個程序可以包含一個或多個執行緒(至少包含一個執行緒) * 執行緒是作業系統排程的最小任務單位 * 如何排程執行緒完全由作業系統決定(程式自己不能決定執行緒何時執行,執行多長時間) 實現多工的三種方法: * 多程序模式(每個程序只有一個執行緒) * 多執行緒模式(一個程序有多個執行緒) * 多程序+多執行緒(複雜度最高) 多程序和多執行緒的對比: * 建立程序比建立執行緒開銷大(尤其是在Windows系統上) * 程序間通訊比執行緒間通訊慢 * 多程序穩定性比多執行緒高(因為在多程序的情況下,一個程序的崩潰不會影響其他程序,而在多執行緒的情況下,任何一個執行緒的崩潰,會直接導致整個程序崩潰)   Java語言內建多執行緒支援,一個Java程式實際上是一個JVM程序,JVM程序用一個主執行緒來執行main()方法,在main()方法中又可以啟動多個執行緒,此外,JVM還有負責垃圾回收的其他工作執行緒等。和單執行緒相比,多執行緒程式設計的特點在於:多執行緒經常需要讀寫共享資料,並且需要同步。例如,播放電影時,就必須由一個執行緒播放視訊,另一個執行緒播放音訊,兩個執行緒需要協調執行,否則畫面和聲音就不同步。因此,多執行緒程式設計的複雜度高,除錯更困難。 ## Java多執行緒基礎   建立一個執行緒物件,並啟動一個新的執行緒。建立執行緒物件的方法有兩種:第一種就是建立MyThread類,去繼承Thread類,覆寫run()方法,建立MyThread例項,呼叫start()啟動執行緒。第二種:如果一個類已經從某個類派生,無法從Thread繼承,就可以通過實現Runnable介面,重寫run()方法,在main()方法中建立Runnable例項,建立Thread例項並傳入Runnable,呼叫start()啟動執行緒。注意:必須呼叫Thread例項的start()方法才能啟動新執行緒,如果我們檢視Thread類的原始碼,會看到start()方法內部呼叫了一個private native void start0()方法,native修飾符表示這個方法是由JVM虛擬機器內部的C程式碼實現的,不是由Java程式碼實現的。 總結:Java用Thread物件表示一個執行緒,通過呼叫start()啟動一個新執行緒,一個執行緒物件只能呼叫一次start()方法;執行緒的執行程式碼寫在run()方法中,一旦run()方法執行完畢,執行緒就結束了;執行緒排程由作業系統決定,程式本身無法決定排程順序。 執行緒的狀態: * New:新建立的執行緒,尚未執行; * Runnable:執行中的執行緒,正在執行run()方法的Java程式碼; * Blocked:執行中的執行緒,因為某些操作被阻塞而掛起; * Waiting:執行中的執行緒,因為某些操作在等待中; * Timed Waiting:執行中的執行緒,因為執行sleep()方法正在計時等待; * Terminated:執行緒已終止,因為run()方法執行完畢。 ![](https://img2020.cnblogs.com/blog/1975191/202007/1975191-20200711075805137-698544293.png) 執行緒終止的原因: * run()方法執行到return語句返回(執行緒正常終止) * run()方法因為未捕獲的異常導致執行緒終止(執行緒意外終止) * 對某個執行緒的Thread例項呼叫stop()方法強制終止(強烈不推薦使用)   通過對另一個執行緒物件呼叫join()方法可以等待其執行結束,可以指定等待時間,超過等待時間執行緒仍然沒有結束就不再等待;對已經執行結束的執行緒呼叫join()方法會立刻返回。   中斷執行緒:如果執行緒需要執行一個長時間任務,就可能需要能中斷執行緒。中斷執行緒就是其他執行緒給該執行緒傳送一個資訊,該執行緒收到訊號後,結束執行run()方法。例如我們從網路下載一個100M的檔案,如果網速很慢,我們等得不耐煩,就可能在下載過程中點“取消”,這時,程式就需要中斷下載執行緒的執行。中斷執行緒需要通過檢測isInterrupted標誌,其他執行緒需要通過呼叫interrupt()方法中斷該執行緒。如果執行緒處於等待狀態,該執行緒會捕獲InterruptedException。捕獲到InterruptedException說明有其他對其執行緒呼叫了interrupt()方法,通常情況下該執行緒應該立刻結束執行。 ``` public class Main { public static void main(String[] args) throws InterruptedException { Thread t = new MyThread(); t.start(); Thread.sleep(1000); t.interrupt(); // 中斷t執行緒 t.join(); // 等待t執行緒結束 System.out.println("end"); } } class MyThread extends Thread { public void run() { Thread hello = new HelloThread(); hello.start(); // 啟動hello執行緒 try { hello.join(); // 等待hello執行緒結束 } catch (InterruptedException e) { System.out.println("interrupted!"); } hello.interrupt(); } } class HelloThread extends Thread { public void run() { int n = 0; while (!isInterrupted()) { n++; System.out.println(n + " hello!"); try { Thread.sleep(100); } catch (InterruptedException e) { break; } } } } ```   還可以通過設定running標誌位,執行緒間共享變數需要使用volatile關鍵字標記,確保執行緒能讀取到更新後的變數值。為什麼要對執行緒間共享的變數用關鍵字volatile宣告?這涉及到Java的記憶體模型。在Java虛擬機器中,變數的值儲存在主記憶體中,但是,當執行緒訪問變數時,它會先獲取一個副本,並儲存在自己的工作記憶體中。如果執行緒修改了變數的值,虛擬機器會在某個時刻把修改後的值回寫到主記憶體,但是,這個時間是不確定的!這會導致如果一個執行緒更新了某個變數,另一個執行緒讀取的值可能還是更新前的。 ![](https://img2020.cnblogs.com/blog/1975191/202007/1975191-20200711085510470-1228580078.png) volatile關鍵字解決的是可見性問題:當一個執行緒修改了某個共享變數的值,其他執行緒能夠立刻看到修改後的值。因此,volatile關鍵字的目的是告訴虛擬機器:每次訪問變數時,總是獲取主記憶體的最新值,每次修改變數後,立刻回寫到主記憶體。   守護執行緒:為其他執行緒服務的執行緒。在JVM中,所有非守護執行緒都執行完畢後,無論有沒有守護執行緒,虛擬機器都會自動退出。因此,JVM退出時,不必關心守護執行緒是否已結束。如何建立守護執行緒呢?方法和普通執行緒一樣,只是在呼叫start()方法前,呼叫setDaemon(true)把該執行緒標記為守護執行緒。 ``` Thread t = new MyThread(); t.setDaemon(true); t.start(); ``` 需要注意的是:守護執行緒不能持有任何需要關閉的資源,例如開啟檔案等,因為虛擬機器退出時,守護執行緒沒有任何機會來關閉檔案,這會導致資料丟失。 ### 多執行緒同步   當多個執行緒同時執行時,執行緒的排程由作業系統決定,程式本身無法決定。因此,任何一個執行緒都有可能在任何指令處被作業系統暫停,然後在某個時間段後繼續執行,如果多個執行緒同時讀寫共享變數,會出現資料不一致的問題。對共享變數進行寫入時,必須保證是原子操作,原子操作是指不能被中斷的一個或一系列操作。例如,對於語句:n = n + 1;看上去是一行語句,實際上對應了3條指令,因此為了保證一系列操作為原子操作,必須保證一系列操作在執行過程中不被其他執行緒執行。ava程式使用synchronized關鍵字對一個物件進行加鎖,synchronized保證了程式碼塊在任意時刻最多隻有一個執行緒能執行。由於synchronized程式碼塊無法併發執行,所以使用synchronized會導致效能下降。如何使用synchronized?首先找出修改共享變數的執行緒程式碼塊,選擇一個例項作為鎖,使用synchronized(lockObject) { ... }。在使用synchronized的時候,不必擔心丟擲異常。因為無論是否有異常,都會在synchronized結束處正確釋放鎖。   多執行緒同時修改變數,會造成邏輯錯誤,需要通過synchronized同步,同步的本質就是給指定物件加鎖,注意加鎖物件必須是同一個例項,對於JVM定義的單個原子操作不需要同步。JVM規範定義了幾種原子操作:基本型別(long和double除外)賦值,例如:int n = m;引用型別賦值,例如:List list = anotherList。long和double是64位資料,JVM沒有明確規定64位賦值操作是不是一個原子操作,不過在x64平臺的JVM是把long和double的賦值作為原子操作實現的。   同步方法:當程式執行synchronized程式碼塊時,首先要獲得synchronized指定的鎖。在我們新增synchronized塊時,我們需要先知道鎖住的哪個物件。讓執行緒自己選擇鎖物件往往會使得程式碼邏輯混亂,也不利於封裝。更好的方法是把synchronized邏輯封裝起來。資料封裝:把同步邏輯封裝到持有資料的例項中。當我們對this進行加鎖時,我們可以使用synchronized來修飾方法,這樣我們就可以把同步程式碼塊自動變成方法識別。下面的兩種寫法是等價的。 ``` public synchronized void add(int n) { n += 1; } public void add(int n) { synchronized(this){ n += 1; } } ``` 而靜態方法鎖住的是Class例項:如下 ``` public class A { private static count; public static synchronized void add(int n){ count += n; } } ``` 等價於下面這種寫法: ``` public class A { private static count; public static void add(int n){ synchronized(A.class) { count += n; } } } ``` Java中執行緒安全的類: * 不變類:例如String, Integer, LocalDate,因為這些類被final修飾,一但建立,例項成員變數就不能改變,不能寫只能讀 * 沒有成員變數的類:例如Math,這些工具只提供了工具方法,自身沒有成員變數 * 正確使用synchronized得類,例如StringBuffer 其它的類都是非執行緒安全的類,不能在多執行緒中共享例項並修改,例如ArrayList,但是可以在多執行緒以只讀的方式共享 ### 死鎖   要執行 synchronized程式碼塊,必須首先獲得指定物件的鎖,Java中的執行緒鎖是可重入鎖。什麼叫可重入鎖?JVM允許同一個執行緒重複獲取同一個鎖,這種能被同一個執行緒反覆獲取的鎖,就叫做可重入鎖。Java的執行緒可以獲取多個不同物件的鎖。不同的執行緒在獲取多個不同物件的鎖的時候,可能會導致死鎖。例如兩個執行緒根被執行下面兩個方法,就會導致死鎖。 ![](https://img2020.cnblogs.com/blog/1975191/202007/1975191-20200711101611171-934026344.png) 死鎖形成的條件:兩個執行緒各自持有不同的鎖,然後各自試圖獲取對方手裡的鎖,造成了雙方無限等待下去,導致死鎖。 ![](https://img2020.cnblogs.com/blog/1975191/202007/1975191-20200711101756083-1923329040.png) 死鎖發生後,沒有任何機制能解除死鎖,只能強制結束JVM程序。如何避免死鎖?執行緒獲取鎖的順序要一致。 ### wait/notify   synchronized解決了多執行緒競爭的問題,但是synchronized並沒有解決多執行緒協調的問題。 ![](https://img2020.cnblogs.com/blog/1975191/202007/1975191-20200711102658476-1198885147.png) wait和notify用於多執行緒協調執行:在synchronized內部可以呼叫wait()使執行緒進入等待狀態,必須在已獲得的鎖物件上呼叫wait()方法,在synchronized內部可以呼叫notify()或notifyAll()喚醒其他等待執行緒,必須在已獲得的鎖物件上呼叫notify()或notifyAll()方法,已喚醒的執行緒還需要重新獲得鎖後才能繼續執行。 ## JUC包   從Java 5開始,引入了一個高階的處理併發的java.util.concurrent包,它提供了大量更高階的併發功能,能大大簡化多執行緒程式的編寫。執行緒同步是因為多執行緒讀寫競爭資源需要同步,Java語言提供了synchronized/wait/notify來實現多執行緒的同步,但是編寫多執行緒同步仍然很困難。jdk1.5提供的高階的java.util.concurrent包,提供了更高階的同步功能,可以簡化多執行緒的編寫,java.util.concurrent.locks包提供的ReentrantLock用於替代synchronized加鎖。因為synchronized是Java語言層面提供的語法,所以我們不需要考慮異常,而ReentrantLock是Java程式碼實現的鎖,我們就必須先獲取鎖,然後在finally中正確釋放鎖。 ![](https://img2020.cnblogs.com/blog/1975191/202007/1975191-20200711104420614-518542029.png) ReentrantLock也是可重入鎖,一個執行緒可多次獲取同一個鎖,lock()方法獲取鎖,和synchronized不同的是,ReentrantLock可以通過tryLock()方法嘗試獲取鎖並指定超時: ``` if (lock.tryLock(1, TimeUnit.SECONDS)) { try { ... } finally { lock.unlock(); } } ```   但是有些時候,這種保護有點過頭。因為我們發現,任何時刻,只允許一個執行緒修改,但是,對於某些方法只讀取資料,不修改資料,它實際上允許多個執行緒同時呼叫,實際上我們想要的是:允許多個執行緒同時讀,但只要有一個執行緒在寫,其他執行緒就必須等待。使用ReadWriteLock可以解決這個問題,它保證只允許一個執行緒寫入(其他執行緒既不能寫入也不能讀取),沒有寫入時,多個執行緒允許同時讀(提高效能)。 ``` public class Counter { private final ReadWriteLock rwlock = new ReentrantReadWriteLock(); private final Lock rlock = rwlock.readLock(); private final Lock wlock = rwlock.writeLock(); private int[] counts = new int[10]; public void inc(int index) { wlock.lock(); // 加寫鎖 try { counts[index] += 1; } finally { wlock.unlock(); // 釋放寫鎖 } } public int[] get() { rlock.lock(); // 加讀鎖 try { return Arrays.copyOf(counts, counts.length); } finally { rlock.unlock(); // 釋放讀鎖 } } } ``` 使用ReadWriteLock時,適用條件是同一個資料,有大量執行緒讀取,但僅有少數執行緒修改。   使用ReentrantLock比直接使用synchronized更安全,可以替代synchronized進行執行緒同步。但是,synchronized可以配合wait和notify實現執行緒在條件不滿足時等待,條件滿足時喚醒,用ReentrantLock我們怎麼編寫wait和notify的功能呢?答案是使用Condition物件來實現wait和notify的功能。 ``` class TaskQueue { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private Queue queue = new LinkedList<>(); public void addTask(String s) { lock.lock(); try { queue.add(s); condition.signalAll(); } finally { lock.unlock(); } } public String getTask() { lock.lock(); try { while (queue.isEmpty()) { condition.await(); } return queue.remove(); } finally { lock.unlock(); } } } ``` 可見,使用Condition時,引用的Condition物件必須從Lock例項的newCondition()返回,這樣才能獲得一個綁定了Lock例項的Condition例項。Condition提供的await()、signal()、signalAll()原理和synchronized鎖物件的wait()、notify()、notifyAll()是一致的,並且其行為也是一樣的:await()會釋放當前鎖,進入等待狀態;signal()會喚醒某個等待執行緒;signalAll()會喚醒所有等待執行緒;喚醒執行緒從await()返回後需要重新獲得鎖。此外,和tryLock()類似,await()可以在等待指定時間後,如果還沒有被其他執行緒通過signal()或signalAll()喚醒,可以自己醒來。   Concurrent集合:java.util.concurrent提供了執行緒安全的Blocking集合:ArrayBlockingQueue。BlockingQueue的意思就是說,當一個執行緒呼叫這個TaskQueue的getTask()方法時,該方法內部可能會讓執行緒變成等待狀態,直到佇列條件滿足不為空,執行緒被喚醒後,getTask()方法才會返回。 介面|執行緒不安全的實現類|執行緒安全的實現類 --|--|-- List|ArrayList|CopyOnWriteArrayList Map|HashMap|ConcurrentHashMap Set|HashSet / TreeSet|CopyOnWriteArraySet Queue|ArrayDeque / LinkedList|ArrayBlockingQueue / LinkedBlockingQueue Deque|ArrayDeque / LinkedList|LinkedBlockingDeque   使用java.util.concurrent包提供的執行緒安全的併發集合可以大大簡化多執行緒程式設計,多執行緒同時讀寫併發集合是安全的,儘量使用Java標準庫提供的併發集合,避免自己編寫同步程式碼。   Atomic:java.util.concurrent.atomic提供了一組原子型別操作,Atomic類是通過無鎖(lock-free)的方式實現的執行緒安全(thread-safe)訪問。它的主要原理是利用了CAS:Compare and Set。如果我們自己通過CAS編寫incrementAndGet(),大概如下: ``` public int incrementAndGet(AtomicInteger var) { int prev, next; do { prev = var.get(); next = prev + 1; } while ( ! var.compareAndSet(prev, next)); return next; } ``` CAS是指,在這個操作中,如果AtomicInteger的當前值是prev,那麼就更新為next,返回true。如果AtomicInteger的當前值不是prev,就什麼也不幹,返回false。通過CAS操作並配合do ... while迴圈,即使其他執行緒修改了AtomicInteger的值,最終的結果也是正確的。用java.util.concurrent.atomic提供的原子操作可以簡化多執行緒程式設計,原子操作實現了無鎖的執行緒安全,適用於計數器,累加器等。 ## ExecutorService   由於建立執行緒需要作業系統資源(執行緒資源、棧空間),頻繁建立和銷燬執行緒需要消耗大量時間。如果可以複用一組執行緒,那麼我們就可以把很多小任務讓一組執行緒來執行,而不是一個任務對應一個新執行緒。這種能接收大量小任務並進行分發處理的就是執行緒池。所以執行緒池內部維護了若干個執行緒,沒有任務的時候,這些執行緒都處於等待狀態。如果有新任務,就分配一個空閒執行緒執行。如果所有執行緒都處於忙碌狀態,新任務要麼放入佇列等待,要麼增加一個新執行緒進行處理。Java標準庫提供了ExecutorService介面表示執行緒池,常用用法如下: ``` // 建立固定大小的執行緒池: ExecutorService executor = Executors.newFixedThreadPool(4); // 提交任務: executor.submit(task1); executor.submit(task2); executor.submit(task3); ``` 因為ExecutorService只是介面,Java標準庫提供的幾個常用實現類有: * FixedThreadPool:執行緒數固定的執行緒池; * CachedThreadPool:執行緒數根據任務動態調整的執行緒池; * SingleThreadExecutor:僅單執行緒執行的執行緒池(只包含一個執行緒,所有的任務只能以單執行緒的形式執行) 建立這些執行緒池的方法都被封裝到Executors這個類中。我們以FixedThreadPool為例,看看執行緒池的執行邏輯: ``` import java.util.concurrent.*; public class Main { public static void main(String[] args) { // 建立一個固定大小的執行緒池: ExecutorService es = Executors.newFixedThreadPool(4); for (int i = 0; i < 6; i++) { es.submit(new Task("" + i)); } // 關閉執行緒池: es.shutdown(); } } class Task implements Runnable { private final String name; public Task(String name) { this.name = name; } @Override public void run() { System.out.println("start task " + name); try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println("end task " + name); } } ```   還有一種任務,需要定期反覆執行,例如,每秒重新整理證券價格。這種任務本身固定,需要反覆執行的,可以使用ScheduledThreadPool。放入ScheduledThreadPool的任務可以定期反覆執行。Java標準庫還提供了一個java.util.Timer類,這個類也可以定期執行任務,但是,一個Timer會對應一個Thread,所以,一個Timer只能定期執行一個任務,多個定時任務必須啟動多個Timer,而一個ScheduledThreadPool就可以排程多個定時任務,所以,我們完全可以用ScheduledThreadPool取代舊的Timer。 總結: * JDK提供了ExecutorService實現了執行緒池功能; * 執行緒池內部維護一組執行緒,可以高效執行大量小任務; * Executors提供了靜態方法建立不同型別的ExecutorService; * 必須呼叫shutdown()關閉ExecutorService; * ScheduledThreadPool可以定期排程多個任務。   Runnable介面有個問題,它的方法沒有返回值。如果任務需要一個返回結果,那麼只能儲存到變數,還要提供額外的方法讀取,非常不便。所以,Java標準庫還提供了一個Callable介面,和Runnable介面比,它多了一個返回值. ``` class Task implements Callable { public String call() throws Exception { return longTimeCalculation(); } } ``` 並且Callable介面是一個泛型介面,可以返回指定型別的結果。現在的問題是,如何獲得非同步執行的結果?如果仔細看ExecutorService.submit()方法,可以看到,它返回了一個Future型別,一個Future型別的例項代表一個未來能獲取結果的物件. ``` ExecutorService executor = Executors.newFixedThreadPool(4); // 定義任務: Callable task = new Task(); // 提交任務並獲得Future: Future future = executor.submit(task); // 從Future獲取非同步執行返回的結果: String result = future.get(); // 可能阻塞 ``` 當我們提交一個Callable任務後,我們會同時獲得一個Future物件,然後,我們在主執行緒某個時刻呼叫Future物件的get()方法,就可以獲得非同步執行的結果。在呼叫get()時,如果非同步任務已經完成,我們就直接獲得結果。如果非同步任務還沒有完成,那麼get()會阻塞,直到任務完成後才返回結果。 一個Future介面表示一個未來可能會返回的結果,它定義的方法有: * get():獲取結果(可能會等待) * get(long timeout, TimeUnit unit):獲取結果,但只等待指定的時間; * cancel(boolean mayInterruptIfRunning):取消當前任務; * isDone():判斷任務是否已完成。   CompletableFuture:從Java 8開始引入了CompletableFuture,它針對Future做了改進,可以傳入回撥物件,當非同步任務結束時,會自動回撥某個物件的方法,非同步任務出錯時,也會自動回撥某個物件的方法,所以當主執行緒設定好回撥後,不再關心非同步任務的執行。 CompletableFuture的基本用法: ``` CompletableFuture cf = CompletableFuture.supplyAsync(非同步執行例項); cf.thenAccept("獲得結果後的操作"); cf.exceptionnally("發生異常時的操作") ``` 建立一個CompletableFuture是通過CompletableFuture.supplyAsync()實現的,它需要一個實現了Supplier介面的物件。可見CompletableFuture的優點是:非同步任務結束時,會自動回撥某個物件的方法;非同步任務出錯時,會自動回撥某個物件的方法;主執行緒設定好回撥後,不再關心非同步任務的執行。如果只是實現了非同步回撥機制,我們還看不出CompletableFuture相比Future的優勢。CompletableFuture更強大的功能是,多個CompletableFuture可以序列執行,例如,定義兩個CompletableFuture,第一個CompletableFuture根據證券名稱查詢證券程式碼,第二個CompletableFuture根據證券程式碼查詢證券價格,這兩個CompletableFuture實現序列操作如下: ``` public class Main { public static void main(String[] args) throws Exception { // 第一個任務: CompletableFuture cfQuery = CompletableFuture.supplyAsync(() -> { return queryCode("中國石油"); }); // cfQuery成功後繼續執行下一個任務: CompletableFuture cfFetch = cfQuery.thenApplyAsync((code) -> { return fetchPrice(code); }); // cfFetch成功後列印結果: cfFetch.thenAccept((result) -> { System.out.println("price: " + result); }); // 主執行緒不要立刻結束,否則CompletableFuture預設使用的執行緒池會立刻關閉: Thread.sleep(2000); } static String queryCode(String name) { try { Thread.sleep(100); } catch (InterruptedException e) { } return "601857"; } static Double fetchPrice(String code) { try { Thread.sleep(100); } catch (InterruptedException e) { } return 5 + Math.random() * 20; } } ``` 總結:CompletableFuture可以指定非同步處理流程: * thenAccept()處理正常結果; * exceptional()處理異常結果; * thenApplyAsync()用於序列化另一個CompletableFuture; * anyOf()和allOf()用於並行化多個CompletableFuture。 ## Fork Join   Java 7開始引入了一種新的Fork/Join執行緒池,它可以執行一種特殊的任務:把一個大任務拆成多個小任務並行執行。Fork/Join執行緒池在Java標準庫中就有應用。Java標準庫提供的java.util.Arrays.parallelSort(array)可以進行並行排序,它的原理就是內部通過Fork/Join對大陣列分拆進行並行排序,在多核CPU上就可以大大提高排序的速度。Fork/Join是一種基於“分治”的演算法:通過分解任務,並行執行,最後合併結果得到最終結果。ForkJoinPool執行緒池可以把一個大任務分拆成小任務並行執行,任務類必須繼承自RecursiveTask或RecursiveAction。使用Fork/Join模式可以進行平行計算以提高效率。 ## ThreadLocal   多執行緒是Java實現多工的基礎,Thread物件代表一個執行緒,我們可以在程式碼中呼叫Thread.currentThread()獲取當前執行緒。Java標準庫提供了一個特殊的ThreadLocal,它可以在一個執行緒中傳遞同一個物件,注意到普通的方法呼叫一定是同一個執行緒執行的。ThreadLocal例項通常總是以靜態欄位初始化如:static ThreadLocal threadLocalUser = new ThreadLocal<>();實際上,可以把ThreadLocal看成一個全域性Map:每個執行緒獲取ThreadLocal變數時,總是使用Thread自身作為key:因此,ThreadLocal相當於給每個執行緒都開闢了一個獨立的儲存空間,各個執行緒的ThreadLocal關聯的例項互不干擾。最後,特別注意ThreadLocal一定要在finally中清除:這是因為當前執行緒執行完相關程式碼後,很可能會被重新放入執行緒池中,如果ThreadLocal沒有被清除,該執行緒執行其他程式碼時,會把上一次的狀態帶進去。ThreadLocal適合在一個執行緒的處理流程中保持上下文(避免了同一引數在所有方法中傳遞)。 【說明】:本文參考了廖雪峰官方網站的Java教程,廖雪峰官方網站的多執行緒教程連結:https://www.liaoxuefeng.com/wiki/1252599548343744/12559437