多執行緒筆記一
多執行緒筆記二
多執行緒筆記三
多執行緒相關問題
1. ThreadPoolExecutor
ThreadPoolExecutor是一種比較常見的處理多執行緒的執行器。你可以配置執行緒池的最小執行緒數,當執行器沒有太多的任務要處理的時候。亦可以配置最大執行緒size,如果有很多工需要處理。一旦當工作負載降下來,執行緒池就會慢慢的減少執行緒數量,知道執行緒數量達到最小值。
ThreadPoolExecutor pool = new ThreadPoolExecutor(
1, // keep at least one thread ready,
// even if no Runnables are executed
5, // at most five Runnables/Threads
// executed in parallel
1, TimeUnit.MINUTES, // idle Threads terminated after one
// minute, when min Pool size exceeded
new ArrayBlockingQueue<Runnable>(10)); // outstanding Runnables are kept here
pool.execute(new Runnable() {
@Override
public void run () {
//code to run
}
});
複製程式碼
- 備註:如果你配置了一個無界佇列的執行緒池。那麼執行緒數量將不會超過corePoolSize .
- 執行緒池引數介紹
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
複製程式碼
如果執行緒池的數量大於corePoolSize並且小於maximumPoolSize,那麼只有線上程池佇列滿了的情況下才會建立新執行緒。
執行緒池的優點:
-
BlockingQueue 能夠避免記憶體溢位的場景。應用的表現不會被佇列的尺寸限制。
-
可以採用不同的Rejection Handler 策略。
- 預設策略:丟擲RejectedExecutionException
- CallerRunsPolicy :如果執行緒池沒有被關閉,那麼就執行,否則丟棄
- DiscardPolicy: 無法執行,直接丟棄
- DiscardOldestPolicy :丟棄隊首的任務。
-
配置自定義的ThreadFactory的好處.
- 定義更有描述性的名稱
- 設定程式的狀態
- 設定執行緒優先權
2.獲取計算任務的值-callable
- 如果你的運算會產生一些以後需要用到的資料,一個簡單的runnable是肯定無法滿足你的。在這種場景下,你可以使用 ExecutorService.submit(Callable) 。這個方法會在任務執行完畢之後返回一個值。
- 這個方法返回一個Future物件,你可以從中獲取任務的值。
// Submit a callable for execution
ExecutorService pool = anExecutorService;
Future<Integer> future = pool.submit(new Callable<Integer>() {
@Override public Integer call() {
//do some computation
return new Random().nextInt();
}
});
// ... perform other tasks while future is executed in a different thread
複製程式碼
-
當你要獲得執行結果時候,呼叫future.get()
-
不確定等到時間的方法 get
try {
// Blocks current thread until future is completed
Integer result = future.get();
catch (InterruptedException || ExecutionException e) {
// handle appropriately
}
複製程式碼
- 在指定時間等待結果
try {
// Blocks current thread for a maximum of 500 milliseconds.
// If the future finishes before that, result is returned,
// otherwise TimeoutException is thrown.
Integer result = future.get(500, TimeUnit.MILLISECONDS);
catch (InterruptedException || ExecutionException || TimeoutException e) {}
複製程式碼
如果計算結果不在需要了,你可以呼叫Future.cancel(boolean)
- cancel(false) 將只會將任務從佇列裡面一處
- cancel(true) 還會打斷當前執行的任務。
3. submit()與execute() 異常處理的區別
- 普通的 execute() 命令一般是用來執行不要結果的任務,submit() 一般是用來分析Future 物件。
- 我們應該關心著兩種異常處理機制不同之處。
- submit() 方法如果不處理就會被框架處理。
示例程式碼如下:
案例1:用excute 命令來執行 runnable 任務,然後上報異常
import java.util.concurrent .*;
import java.util .*;
public class ExecuteSubmitDemo {
public ExecuteSubmitDemo() {
System.out.println("creating service");
ExecutorService service = Executors.newFixedThreadPool(2);
//ExtendedExecutor service = new ExtendedExecutor();
for (int i = 0; i < 2; i++) {
service.execute(new Runnable() {
public void run() {
int a = 4, b = 0;
System.out.println("a and b=" + a + ":" + b);
System.out.println("a/b:" + (a / b));
System.out.println("Thread Name in Runnable after divide by
zero:"+Thread.currentThread().getName());
}
});
}
service.shutdown();
}
public static void main(String args[]) {
ExecuteSubmitDemo demo = new ExecuteSubmitDemo();
}
}
class ExtendedExecutor extends ThreadPoolExecutor {
public ExtendedExecutor() {
super(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
}
// ...
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t == null && r instanceof Future<?>) {
try {
Object result = ((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null)
System.out.println(t);
}
}
複製程式碼
輸出:
creating service
a and b=4:0
a and b=4:0
Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-2"
java.lang.ArithmeticException: / by zero
at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:15)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
java.lang.ArithmeticException: / by zero
at ExecuteSubmitDemo$1.run(ExecuteSubmitDemo.java:15)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:744)
複製程式碼
案例2:用submit 替換excute ,service.submit(new Runnable() 在這個案例中,異常被框架吃了。
輸出
creating service
a and b=4:0
a and b=4:0
複製程式碼
案例3 將newFixedThreadPool換成ExtendedExecutor
ExtendedExecutor service = new ExtendedExecutor();
複製程式碼
輸出:
creating service
a and b=4:0
java.lang.ArithmeticException: / by zero
a and b=4:0
java.lang.ArithmeticException: / by zero
複製程式碼
- 我們評估上面兩個案例,得出答案,使用自定義的執行緒池來處理異常。
- 其他解決上述問題的方法,如果你使用普通的 ExecutorService & submit ,使用get來獲取結果。那麼請捕獲上述程式碼裡面捕獲的三個異常。自定義ThreadPoolExecutor 有一個好處,即是隻需要在一個地方捕捉異常。
4. 處理拒絕執行
如果:
- 你試圖向一個一個關閉的Executor 提交任務
- 佇列已經滿了,執行緒數已經達到最大值
- 那麼RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)將會被呼叫。
- 上述方法的預設行為是丟擲一個RejectedExecutionException異常。但是我們有更多的策略可以選擇:
- ThreadPoolExecutor.AbortPolicy (default, will throw REE)
- ThreadPoolExecutor.CallerRunsPolicy (executes task on caller's thread - blocking it)
- ThreadPoolExecutor.DiscardPolicy (silently discard task)
- ThreadPoolExecutor.DiscardOldestPolicy (silently discard oldest task in queue and retry execution of the new task)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) // <--
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) // <
複製程式碼
- 你也可以自己實現RejectedExecutionHandler 介面來自定義執行的行為方式。
void rejectedExecution(Runnable r, ThreadPoolExecutor executor)
複製程式碼
5 :發射後不管 - Runnable Tasks
Executors接受一個 java.lang.Runnable 引數物件,用來處理耗時或者計算量較大的任務。
- 使用方法如下:
Executor exec = Executors.newCachedThreadPool();
exec.ex(new Runnable() {
@Override public void run () {
//offloaded work, no need to get result backJava® Notes for Professionals 696
}
});
複製程式碼
注意使用這個excutor ,你獲取不到任何資料返回值。在Java8 中你可以應用lamda 來簡化程式碼
Executor exec = anExecutor;
exec.execute(() -> {
//offloaded work, no need to get result back
});
複製程式碼
6:不同型別的併發的構造的使用案例:
- ExecutorService
- ExecutorService executor = Executors.newFixedThreadPool(50);
- 這種用法事發簡單。它隱藏了很多執行緒池的底層實現。
- 在任務數量較小我傾向使用這種方式,它不會讓記憶體增長過快也不會降低系統的效能。如果你有cpu/記憶體 方面的約束,我建議使用執行緒池的時候對執行緒的容量以及 處理拒絕執行的任務。
- CountDownLatch
- CountDownLatch 使用固定的數初始化。這個數會隨著countdown 方法被呼叫而減少。我們可以通過呼叫await方法讓當前 執行緒等待執行緒執行直至數量降至0。
使用條件:
-
1. 實現最大數量的非同步任務:在某些情況,我們可以在同時啟動一組執行緒 複製程式碼
-
2. 在開始執行之前等待其他執行緒執行 複製程式碼
-
3. 死鎖檢測 複製程式碼
- ThreadPoolExecutor
- 提供了更多的控制。如果程式被限制了要執行一組延遲Runnable/Callable任務,你可以通過設定最大容積來使用有界陣列。一旦佇列達到最大容量,你可以定義一個 RejectionHandler 來處理拒絕的任務。Java提供了四種型別的RejectionHandler 不同的實現策略。上述已經提及了,這裡就不進行贅述了。
- 你可能不知道 ForkJoinPool
-
ForkJoinPool是Java 7 引入的。ForkJoinPool與 ExecutorService類似,但是還是有一點區別。ForkJoinPool與非常容易實現任務分割成一些小任務。當某些佇列任務執行完畢之後,他便會從其他佇列的尾部偷取一個任務進行執行。 複製程式碼
-
Java 8 引入了一個新的api ,你不需要新建RecursiveTask 任務就只可以直接使用ForkJoinPool; 複製程式碼
public static ExecutorService newWorkStealingPool()
複製程式碼
建立work-stealing執行緒池會根據處理器的並行程度最大程度的利用處理器
- 預設來說,他會採用cpu 的核心來作為引數。
上述提到的四種原理互不影響。你可以根據你自己的需求,選用合適的框架進行使用
7. 使用ExecutorService等待所有任務執行完畢
- 我們先來看一眼方法:
-
ExecutorService invokeAll() 複製程式碼
當所有任務都執行完畢會返回一個Futures list。
示例程式碼:
import java.util.concurrent .*;
import java.util .*;
public class InvokeAllDemo {
public InvokeAllDemo() {
System.out.println("creating service");
ExecutorService service =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
List<MyCallable> futureList = new ArrayList<MyCallable>();
for (int i = 0; i < 10; i++) {
MyCallable myCallable = new MyCallable((long) i);
futureList.add(myCallable);
}
System.out.println("Start");
try {
List<Future<Long>> futures = service.invokeAll(futureList);
} catch (Exception err) {
err.printStackTrace();
}
System.out.println("Completed");
service.shutdown();
}
public static void main(String args[]) {
InvokeAllDemo demo = new InvokeAllDemo();
}
class MyCallable implements Callable<Long> {
Long id = 0L;
public MyCallable(Long val) {
this.id = val;
}
public Long call() {
// Add your business logic
return id;
}
}
}
複製程式碼
8. 使用不同型別的ExecutorService
Executors 返回不同型別的執行緒池來滿足不同需求
1. 1. public static ExecutorService newSingleThreadExecutor()
建立一個單工作執行緒來操作一個無界佇列
它與 newFixedThreadPool(1) 和 newSingleThreadExecutor()的區別Java doc 是這樣說的:
與類似的 newFixedThreadPool(1)相比其不保證重新配置異常執行緒來使用替代執行緒。
- 這就意味著newFixedThreadPool可以被程式重新配置類似如下:((ThreadPoolExecutor) fixedThreadPool).setMaximumPoolSize(10),這在newSingleThreadExecutor 是不可能的。
- 使用場景: 1. 你打算按照提交順序來執行任務 2. 你需要一個執行緒來執行你的所有請求 缺點:無界佇列有風險
2. public static ExecutorService newFixedThreadPool(int nThreads)
建立一個固定數量的執行緒池,複用執行緒來操作一個共享的無界佇列。在任何時刻,大多數執行緒都會在執行任務的時候被啟用。如果提交了額外的任務,在所有執行緒都是啟用狀態的時候,那麼他們將會阻塞知道有空閒執行緒。
使用場景:
-
可以通過獲取cpu的數量來提高執行緒執行情況。 複製程式碼
-
你可以選擇執行緒池的最大執行緒數 複製程式碼
缺點:無界佇列有風險
3. public static ExecutorService newCachedThreadPool()
建立一個按需分配的執行緒池,會重複利用之前已經建立的執行緒。
使用條件:
-
一些執行時間段的非同步任務 複製程式碼
- 缺點:
1. 無界佇列有風險
2. 如果所有的存在的執行緒都在busy 那麼每個任務都會新建一個執行緒。如果任務長期執行,將會建立大量的執行緒,這將會降低系統的效能。在這種情況下推薦newFixedThreadPool。
4. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
建立一個執行緒池,是的能夠在指定時間段之後執行任務,或者每隔一段時間執行
使用場景:
-
處理週期性的事件 複製程式碼
缺點:無界佇列有風險。
5. public static ExecutorService newWorkStealingPool()
建立任務偷取型的執行緒池,取決於處理器的併發水平
使用場景
-
將任務分割成很多子任務 複製程式碼
-
對於空閒執行緒處理能力較高 複製程式碼
缺點:無界佇列有風險
- 你可能發現了一個通用的缺陷:無界佇列。它會與ThreadPoolExecutor一起繫結使用。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler)
複製程式碼
使用執行緒池你可以:
- 動態控制執行緒池尺寸
- 設定BlockingQueue 容積
- 定了拒絕策略
- 自定義 CustomThreadFactory 可以有一些附帶功能
9. 排程執行緒在固定的時間執行,或者在延遲一段時間之後,或者重複執行
- ScheduledExecutorService 提供了一個方法用來排程執行一次性或者重複的任務。
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
複製程式碼
- 在普通的執行緒池方法之外,ScheduledExecutorService新增了4個方法來排程任務,然後返回ScheduledFuture 物件。
在固定時間之後開始一個任務
- 如下程式碼展示了在十分鐘之後執行一個任務:
ScheduledFuture<Integer> future = pool.schedule(new Callable<>() {
@Override
public Integer call() {
// do something
return 42;
}
},10, TimeUnit.MINUTES);
複製程式碼
以固定的頻率來執行任務
- 如下程式碼展示了在十分鐘之後開始執行一個任務,然後每隔一分鐘開始重複任務:
ScheduledFuture<?> future = pool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// do something
}
},10, 1, TimeUnit.MINUTES);
複製程式碼
任務會一直執行到執行緒池被關閉,future 被去小,或者某個任務發生了異常。
10. 執行緒池的使用
- 執行緒池大多數情況下都是通過呼叫 ExcutorService 的方法建立
- 如下方法可以用來提交任務
- submit: 執行提交的任務返回一個future 物件
- execute: 執行任務並不期望返回任何值
- invokeAll:執行一組任務,並且得到一個返回值列表
- invokeAny:執行所有任務,獲得其中一個正確執行的(沒有異常的),其餘沒有執行的任務或被取消。
- 一旦你使用了 shutdown() 來終止執行緒池。這個操作會阻塞任務的提交。如果向等到所有任務都被執行,你可以用 awaitTermination 或isShutdown() 來包裹程式碼。