Java多執行緒編程基礎知識匯總
前言
本文參考了廖雪峰官方網站的Java教程,廖雪峰官方網站的多執行緒教程鏈接://www.liaoxuefeng.com/wiki/1252599548343744/1255943750561472
多執行緒簡介
多任務
現代作業系統(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()方法執行完畢。
執行緒終止的原因:
- 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虛擬機中,變數的值保存在主記憶體中,但是,當執行緒訪問變數時,它會先獲取一個副本,並保存在自己的工作記憶體中。如果執行緒修改了變數的值,虛擬機會在某個時刻把修改後的值回寫到主記憶體,但是,這個時間是不確定的!這會導致如果一個執行緒更新了某個變數,另一個執行緒讀取的值可能還是更新前的。
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
同步方法:當程式執行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的執行緒可以獲取多個不同對象的鎖。不同的執行緒在獲取多個不同對象的鎖的時候,可能會導致死鎖。例如兩個執行緒根被執行下面兩個方法,就會導致死鎖。
死鎖形成的條件:兩個執行緒各自持有不同的鎖,然後各自試圖獲取對方手裡的鎖,造成了雙方無限等待下去,導致死鎖。
死鎖發生後,沒有任何機制能解除死鎖,只能強制結束JVM進程。如何避免死鎖?執行緒獲取鎖的順序要一致。
wait/notify
synchronized解決了多執行緒競爭的問題,但是synchronized並沒有解決多執行緒協調的問題。
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中正確釋放鎖。
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<String> 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<String> {
public String call() throws Exception {
return longTimeCalculation();
}
}
並且Callable介面是一個泛型介面,可以返回指定類型的結果。現在的問題是,如何獲得非同步執行的結果?如果仔細看ExecutorService.submit()方法,可以看到,它返回了一個Future類型,一個Future類型的實例代表一個未來能獲取結果的對象.
ExecutorService executor = Executors.newFixedThreadPool(4);
// 定義任務:
Callable<String> task = new Task();
// 提交任務並獲得Future:
Future<String> 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<String> 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<String> cfQuery = CompletableFuture.supplyAsync(() -> {
return queryCode("中國石油");
});
// cfQuery成功後繼續執行下一個任務:
CompletableFuture<Double> 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