一、線程池作用
線程池作用就是限制系統中執行線程的數量。
根據系統的環境情況,可以自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用線程池控制線程數量,其他線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待。當一個新任務需要運行時,如果線程池中有等待的工作線程,就可以開始運行了;否則進入等待隊列。
基於生產者-消費者模式,其提交任務的線程相當於生產者,執行任務的線程相當於消費者,並用Runnable來表示任務
二、重要接口和類
1.接口Executor
定義了一個接收Runnable對象的方法execute
2.接口ExecutorService
是一個比Executor使用更廣泛的子類接口,其提供了生命週期(運行、關閉、已終止)管理的方法,以及可跟蹤一個或多個異步任務執行狀況返回Future的方法。Future 表示一個任務的生命週期,並且提供了相應的方法來判斷是否已經完成或取消,以及獲取任務的結果和取消任務等。
創建一個什麼樣的ExecutorService的實例(即線程池)需要根據具體應用場景而定,不過Java給我們提供了一個Executors工廠類,它可以幫助我們很方便的創建各種類型ExecutorService線程池。
2.1 ExecutorService的使用
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();
2.2 ExecutorService的執行
ExecutorService有如下幾個執行方法:
- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(...)
- invokeAll(...)
2.2.1 execute(Runnable)
這個方法接收一個Runnable實例,並且異步的執行,請看下面的實例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
executorService.shutdown();
這個方法有個問題,就是沒有辦法獲知task的執行結果。如果我們想獲得task的執行結果,我們可以傳入一個Callable的實例(下面會介紹)。
2.2.2 submit(Runnable)
submit(Runnable)和execute(Runnable)區別是前者可以返回一個Future對象,通過返回的Future對象,我們可以檢查提交的任務是否執行完畢,請看下面執行的例子:
Future future = executorService.submit(new Runnable() {
public void run() {
System.out.println("Asynchronous task");
}
});
future.get(); //returns null if the task has finished correctly.
如果任務執行完成,future.get()方法會返回一個null。注意,future.get()方法會產生阻塞。
2.2.3 submit(Callable)
submit(Callable)和submit(Runnable)類似,也會返回一個Future對象,但是除此之外,submit(Callable)接收的是一個Callable的實現,Callable接口中的call()方法有一個返回值,可以返回任務的執行結果,而Runnable接口中的run()方法是void的,沒有返回值。請看下面實例:
Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
System.out.println("Asynchronous Callable");
return "Callable Result";
}
});
System.out.println("future.get() = " + future.get());
如果任務執行完成,future.get()方法會返回Callable任務的執行結果。注意,future.get()方法會產生阻塞,直到call方法結束返回結果。
2.2.4 invokeAny(…)
invokeAny(…)方法接收的是一個Callable的集合,執行這個方法不會返回Future,但是會返回所有Callable任務中其中一個任務的執行結果。這個方法也無法保證返回的是哪個任務的執行結果,反正是其中的某一個。請看下面實例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();
大家可以嘗試執行上面代碼,每次執行都會返回一個結果,並且返回的結果是變化的,可能會返回“Task2”也可是“Task1”或者其它。
2.2.5 invokeAll(…)
invokeAll(…)與 invokeAny(…)類似也是接收一個Callable集合,但是前者執行之後會返回一個Future的List,其中對應着每個Callable任務執行後的Future對象。情況下面這個實例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Set<Callable<String>> callables = new HashSet<Callable<String>>();
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 2";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
return "Task 3";
}
});
List<Future<String>> futures = executorService.invokeAll(callables);
for(Future<String> future : futures){
System.out.println("future.get = " + future.get());
}
executorService.shutdown();
2.3 ExecutorService的關閉
當我們使用完成ExecutorService之後應該關閉它,否則它裏面的線程會一直處於運行狀態。
如果要關閉ExecutorService中執行的線程,我們可以調用ExecutorService.shutdown()方法。在調用shutdown()方法之後,ExecutorService不會立即關閉,但是它不再接收新的任務,直到當前所有線程執行完成纔會關閉,所有在shutdown()執行之前提交的任務都會被執行。
如果我們想立即關閉ExecutorService,我們可以調用ExecutorService.shutdownNow()方法。這個動作將跳過所有正在執行的任務和被提交還沒有執行的任務。但是它並不對正在執行的任務做任何保證,有可能它們都會停止,也有可能執行完成。
3.接口ScheduledExecutorService
3.1 scheduleAtFixedRate() 按指定頻率週期執行某個任務
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
command:執行線程
initialDelay:初始化延時
period:兩次開始執行最小間隔時間
unit:計時單位
實例:
private static ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
static {
service.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延遲10s開始執行,每隔100s重新執行一次任務");
}
}, 10, 100, TimeUnit.SECONDS);
}
3.2 scheduleWithFixedDelay() 按指定頻率間隔執行某個任務
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
command:執行線程
initialDelay:初始化延時
period:前一次執行結束到下一次執行開始的間隔時間(間隔執行延遲時間)
unit:計時單位
實例:
private static ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
static {
service.scheduleWithFixedDelay(new Runnable() {
public void run() {
System.out.println("10秒後開始執行,本次執行結束後延遲100s開始下次執行。");
}
}, 10, 100, TimeUnit.SECONDS);
}
3.3 週期定時執行某個任務
/**
* 每天晚上8點執行一次
* 每天定時安排任務進行執行
*/
public static void executeEightAtNightPerDay() {
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
long oneDay = 24 * 60 * 60 * 1000;
long initDelay = getTimeMillis("20:00:00") - System.currentTimeMillis();
initDelay = initDelay > 0 ? initDelay : oneDay + initDelay;
executor.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("do something");
},
initDelay,
oneDay,
TimeUnit.MILLISECONDS);
}
/**
* 獲取指定時間對應的毫秒數
* @param time "HH:mm:ss"
* @return
*/
private static long getTimeMillis(String time) {
try {
DateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
DateFormat dayFormat = new SimpleDateFormat("yy-MM-dd");
Date curDate = dateFormat.parse(dayFormat.format(new Date()) + " " + time);
return curDate.getTime();
} catch (ParseException e) {
e.printStackTrace();
}
return 0;
}
4.類Executors
通過 Executors 中的靜態工廠方法來創建線程池。
4.1 newFixedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
創建一個定長線程池,定長線程池的大小最好根據系統資源進行設置,可控制線程最大併發數,超出的線程會在隊列中等待。
每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。
newFixedThreadPool內部有個任務隊列,假設線程池裏有3個線程,提交了5個任務,那麼後兩個任務就放在任務隊列了,即使前3個任務sleep或者堵塞了,也不會執行後兩個任務,除非前三個任務有執行完的
實例:
public class Test {
public static void main(String[] args) throws IOException, InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(2);
for (int i = 0; i < 6; i++) {
final int index = i;
System.out.println("task: " + (i + 1));
Runnable run = new Runnable() {
@Override
public void run() {
System.out.println("thread start" + index);
try {
Thread.sleep(Long.MAX_VALUE);//執行很久。。。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread end" + index);
}
};
service.execute(run);
}
service.shutdown();
}
}
輸出:
task: 1
task: 2
thread start0
thread start1
task: 3
task: 4
task: 5
task: 6
6個任務,只有二個任務在執行,4個在任務隊列,等候執行。
4.2 newCachedThreadPool()
創建一個可緩存線程池,如果線程池中的線程在60秒未被使用就將被移除,在執行新的任務時,當線程池中有之前創建的可用線程就重用可用線程,否則就新建一條線程
public class Test {
public static void main(String[] args) {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(index);
}
});
}
}
}
線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。
對於執行很多短期異步任務的程序而言,可提高程序性能。
4.3 newScheduledThreadPool()
創建一個定長線程池,支持定時及週期性任務執行。實例同【3.接口ScheduledExecutorService】
4.4 newSingleThreadExecutor()
創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
public class Test {
public static void main(String[] args) {
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}
結果依次輸出,相當於順序執行各個任務。
5.類ThreadPoolExecutor
private static ExecutorService exec = new ThreadPoolExecutor(8, 8, 0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(100000),
new ThreadPoolExecutor.CallerRunsPolicy());
5.1 常用構造方法
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
corePoolSize | 線程(核心)池維護線程的最少數量 |
maximumPoolSize | 線程(最大)池維護線程的最大數量 |
keepAliveTime | 線程池維護線程所允許的空閒時間 |
unit | 線程池維護線程所允許的空閒時間的單位 |
workQueue | 線程池所使用的緩衝隊列 |
handler | 線程池對拒絕任務的處理策略 |
5.1.1
一個任務通過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是Runnable類型對象的run()方法。當一個任務通過execute(Runnable)方法欲添加到線程池時:
l 如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閒狀態,也要創建新的線程來處理被添加的任務。
l 如果此時線程池中的數量等於 corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務被放入緩衝隊列。
l 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量小於maximumPoolSize,建新的線程來處理被添加的任務。
l 如果此時線程池中的數量大於corePoolSize,緩衝隊列workQueue滿,並且線程池中的數量等於maximumPoolSize,那麼通過 handler所指定的策略來處理此任務。也就是:處理任務的優先級爲:核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
l 當線程池中的線程數量大於 corePoolSize時,如果某線程空閒時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。
5.1.2 handler有四個選擇
默認new ThreadPoolExecutor.AbortPolicy() | 處理程序遭到拒絕將拋出運行時RejectedExecutionException |
new ThreadPoolExecutor.CallerRunsPolicy() | 當拋出RejectedExecutionException異常時,會調用rejectedExecution方法 |
new ThreadPoolExecutor.DiscardOldestPolicy() | 拋棄舊的任務,如果執行程序尚未關閉,則位於工作隊列頭部的任務將被刪除,然後重試執行程序(如果再次失敗,則重複此過程) |
new ThreadPoolExecutor.DiscardPolicy() | 拋棄當前的任務,不能執行的任務將被刪除 |
實例:(實例原文http://blog.csdn.net/pfnie/article/details/52755769)
寫一個task:
public class Task implements Runnable {
protected String name;
public Task(String name) {
super();
this.name = name;
}
@Override
public void run() {
try {
System.out.println(this.name + " is running.");
Thread.sleep(500);
} catch (Exception e) {
}
}
}
1) AbortPolicy 示例
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class AbortPolicyDemo {
public static void main(String[] args) {
// 創建線程池。線程池的"最大池大小"和"核心池大小"都爲1,"線程池"的阻塞隊列容量爲1。
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 0,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1));
// 設置線程池的拒絕策略爲AbortPolicy
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
// 設置線程池的拒絕策略爲CallerRunsPolicy
// pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 設置線程池的拒絕策略爲DiscardPolicy
// pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
// 設置線程池的拒絕策略爲DiscardOldestPolicy
// pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
try {
// 新建10個任務,並將它們添加到線程池中
for (int i = 0; i < 10; i++) {
Runnable myTask = new Task("task-"+i);
pool.execute(myTask);
}
} catch (RejectedExecutionException e) {
e.printStackTrace();
// 關閉線程池
pool.shutdown();
}
}
}
某一次運行結果,直接已異常的形式拋出:
task-0 is running.
java.util.concurrent.RejectedExecutionException: Task websocket.blocktest.Task@58777255 rejected from java.util.concurrent.ThreadPoolExecutor@470ae2bf[Running, pool size = 1, active threads = 1, queued tasks = 1, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
at websocket.blocktest.Test.main(Test.java:22)
task-1 is running.
2)CallerRunsPolicy 示例
// 設置線程池的拒絕策略爲CallerRunsPolicy
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
某一次運行結果,當有新任務添加到線程池被拒絕時,線程池會將被拒絕的任務添加到”線程池正在運行的線程”中去運行:
task-2 is running.
task-0 is running.
task-3 is running.
task-1 is running.
task-5 is running.
task-4 is running.
task-7 is running.
task-6 is running.
task-9 is running.
task-8 is running.
3)DiscardPolicy 示例
// 設置線程池的拒絕策略爲DiscardPolicy
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
某一次運行結果,線程池pool的”最大池大小”和”核心池大小”都爲1,這意味着”線程池能同時運行的任務數量最大隻能是1”。線程池pool的阻塞隊列是ArrayBlockingQueue,ArrayBlockingQueue是一個有界的阻塞隊列,ArrayBlockingQueue的容量爲1。這也意味着線程池的阻塞隊列只能有一個線程池阻塞等待。由此可知,線程池中共運行了2個任務。第1個任務直接放到Worker中,通過線程去執行;第2個任務放到阻塞隊列中等待。其他的任務都被丟棄了。
task-0 is running.
task-1 is running.
4)DiscardOldestPolicy 示例
// 設置線程池的拒絕策略爲DiscardOldestPolicy
pool.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
某一次運行結果,當有新任務添加到線程池被拒絕時,線程池會丟棄阻塞隊列中末尾的任務,然後將被拒絕的任務添加到末尾。
task-0 is running.
task-9 is running.
5.2 ThreadPoolExecutor配置(加深理解)
5.2.1 線程的創建與銷燬
1)核心池大小、最大池大小和存活時間共同管理着線程的創建與銷燬。
2)核心池的大小是目標的大小;線程池的實現試圖維護池的大小;即使沒有任務執行,池的大小也等於核心池的大小,並直到工作隊列充滿前,池都不會創建更多的線程。如果當前池的大小超過了核心池的大小,線程池就會終止它。
3)最大池的大小是可同時活動的線程數的上限。
4)如果一個線程已經閒置的時間超過了存活時間,它將成爲一個被回收的候選者。
5)newFixedThreadPool工廠爲請求的池設置了核心池的大小和最大池的大小,而且池永遠不會超時
6)newCacheThreadPool工廠將最大池的大小設置爲Integer.MAX_VALUE,核心池的大小設置爲0,超時設置爲一分鐘。這樣創建了無限擴大的線程池,會在需求量減少的情況下減少線程數量。
5.2.2 管理
1)ThreadPoolExecutor允許你提供一個BlockingQueue來持有等待執行的任務。任務排隊有3種基本方法:無限隊列、有限隊列和同步移交。
2)newFixedThreadPool和newSingleThreadExectuor默認使用的是一個無限的 LinkedBlockingQueue。如果所有的工作者線程都處於忙碌狀態,任務會在隊列中等候。如果任務持續快速到達,超過了它們被執行的速度,隊列也會無限制地增加。穩妥的策略是使用有限隊列,比如ArrayBlockingQueue或有限的LinkedBlockingQueue以及 PriorityBlockingQueue。
3)對於龐大或無限的池,可以使用SynchronousQueue,完全繞開隊列,直接將任務由生產者交給工作者線程
4)可以使用PriorityBlockingQueue通過優先級安排任務
6.類ScheduledThreadPoolExecutor
平時我們在執行一個定時任務時,會採用Time和TimeTask來組合處理,但是Timer和TimerTask存在一些缺陷:
1)Timer只創建了一個線程。當你的任務執行的時間超過設置的延時時間將會產生一些問題。
2)Timer創建的線程沒有處理異常,因此一旦拋出非受檢異常,該線程會立即終止。
JDK 5.0以後推薦使用ScheduledThreadPoolExecutor。該類屬於Executor Framework,它除了能處理異常外,還可以創建多個線程解決上面的問題。
實例:
public class Test {
public static void main(String[] args) throws InterruptedException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ScheduledThreadPoolExecutor pool = new ScheduledThreadPoolExecutor(5);
for (int i = 0; i < 5; i ++){
final int temp = i + 1;
//jdk8 lambda表達式
pool.schedule(() -> {
System.out.println("第"+temp+"個炸彈爆炸時間:" + simpleDateFormat.format(new Date()));
}, temp * 5, TimeUnit.SECONDS);
//隔2秒後開始執行任務,並且在上一次任務開始後隔一秒再執行一次;
//pool.scheduleWithFixedDelay(new MyTask(), 2, 1, TimeUnit.SECONDS);
//隔6秒後執行一次,但只會執行一次。
//pool.schedule(new MyTask(), 6, TimeUnit.SECONDS);
}
pool.shutdown();
System.out.println("end main時間:" + simpleDateFormat.format(new Date()));
}
private static class MyTask implements Runnable{
@Override
public void run() {
//捕獲所有的異常,保證定時任務能夠繼續執行
try{
System.out.println("任務開始...");
// 業務...
System.out.println("任務結束...");
}catch (Throwable e) {
// 什麼也不做
}
}
}
}
在JAVAWEB開發中,執行定時任務有一個更好的選擇: Quartz