一、Executor介紹
爲了更好的控制多線程,JDK提供了一套線程框架Executor,在java.util.concurrent包中。其中有一個比較重要的類:Executors,它扮演線程工廠的角色,可以通過Executors可以創建特定功能的線程池。
二、Executors創建線程的方法
/**
* 創建固定數量的線程池,該方法的線程數始終不變。
* 當有任務提交時,若線程池空閒,則立即執行 ;若沒有,則會暫存在一個任務隊列當中等待有空閒的線程去執行
*/
Executors.newFixedThreadPool(5);
/**
* 創建一個線程的線程池。
* 當有任務提交時,若線程池空閒,則立即執行 ;若沒有,則會暫存在一個任務隊列當中等待有空閒的線程去執行
*/
Executors.newSingleThreadExecutor();
/**
* 創建一個可根據實際情況調整線程數量的線程池,不限制最大線程數量。
* 有任務時,創建線程;無任務,不創建線程。
* 如果沒有任務60s後自動回收空閒線程
*/
Executors.newCachedThreadPool();
/**
* 該方法返回ScheduledExecutorService對象。該線程池可以指定線程數量。
*/
Executors.newScheduledThreadPool(10);
以上四種方法實際上都是通過ThreadPoolExecutor來實現的,當以上四種線程池不能滿足需求時,可以使用ThreadPoolExecutor來創建自定義線程池。
三、自定義線程池
ThreadPoolExecutor的參數
/**
*
* @param corePoolSize 線程池初始化時,創建的線程數量
* @param maximumPoolSize 線程池最大支持的線程數量
* @param keepAliveTime 空閒線程存活的時間
* @param unit 空閒線程存活時間的單位 keepAliveTime和unit一起使用
* @param workQueue 當線程池沒有空閒線程時,用來緩存任務的隊列
* @param threadFactory
* @param handler 如果沒有空閒線程,用來緩存隊列也滿的時候,採取的拒絕策略
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
) { ...}
瞭解了ThreadPoolExecutor之後,再來看一下JDK封裝好的線程池。
1、newFixedThreadPool源碼
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
corePoolSize和maximumPoolSize都是一樣的,即線程數量固定;
keepAliveTime爲0,即線程一直會存在,不會被回收;
這裏用的緩存隊列爲無界的LinkedBlockingQueue。
2、newSingleThreadExecutor源碼
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
coreSize和maximumPoolSize都是1,即線程池中只能有1個線程;
3、newCachedThreadPool源碼
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
corePoolSize爲0,maximum爲0x7fffffff,即最大int,在這裏認爲是無限大;
空閒線程的存活的時間爲60s;
SynchrousQueue中不能存放任何元素;
當有任務需要處理時,該線程池就會創建線程執行任務,執行完畢後,線程60s後會被回收。
4、newScheduledThreadPool源碼
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
maximumPoolSize爲無限大;
這裏用的緩存隊列爲DelayedWorkQueue,延遲隊列;
四、自定義線程池的使用詳情
構造方法中的隊列類型對線程池的使用有很大影響:
1、使用有界隊列時:
若有新的任務需要執行,如果線程池中的線程數小於corePoolSize,則會創建線程,執行任務;若大於corePoolSize,則會將任務加入到隊列中:如果隊列已滿,在總線程數不大於maximumPoolSize前提下,創建線程;若總線程數大於maximumPoolSize,則執行拒絕策略。
2、使用無界隊列時:
有新的任務需要執行時,如果線程池中的線程數小於corePoolSize,則會創建線程,執行任務;當達到corePoolSize時,就不會繼續增加。若後續仍有任務需要執行時,則任務直接進入隊列,直至系統資源耗盡或有空閒線程。
五、代碼
使用有界隊列時:
package ConcurrentProgramming.High.ThreadPoolExecute1;
import lombok.Data;
/**
* @Author: zdj
* @Description: 任務 需要實現Runnable接口
* @Date: 2019年03月18日 16:28
*/
@Data
public class Task implements Runnable{
private String id;
private String name;
public Task(String id, String name) {
this.id = id;
this.name = name;
}
public void run() {
System.out.println("run Task Id " + this.id);
try {
Thread.sleep(5*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String toString(){
return id;
}
}
package ConcurrentProgramming.High.ThreadPoolExecute1;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @Author: zdj
* @Description: 自定義拒絕策略 必須實現RejectedExecutionHandler接口
* @Date: 2019年03月18日 16:53
*/
public class MyReject implements RejectedExecutionHandler {
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("該任務被拒絕了,被拒絕任務的id爲" + r.toString());
}
}
package ConcurrentProgramming.High.ThreadPoolExecute1;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Author: zdj
* @Description: 自定義線程池使用有界隊列的時候
* 創建6個任務:任務1會被直接執行,任務234會被放入有界隊列ArrayBlockingQueue中,
* 繼續創建線程執行任務5,此時隊列已滿,也沒有空閒線程,任務6,會執行拒絕策略
* @Date: 2019年03月18日 16:25
*/
public class UseThreadPoolExecute1 {
public static void main(String[] args) {
/**
* 在使用有界隊列時,若有新的任務需要執行,如果線程池實際線程數小於corePoolSize,則優先創建線程,
* 若大於corePoolSize,則會將任務加入隊列,
* 若隊列已滿,則在總線程數不大於maximumPoolSize的前提下,創建新的線程,
* 若線程數大於maximumPoolSize,則執行拒絕策略。或其他自定義方式。
*
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
1, 2,60,
TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(3),
//拒絕策略 一般使用自定義的
// new ThreadPoolExecutor.DiscardOldestPolicy()
//自定義拒絕策略
new MyReject()
);
Task task1 =new Task("1","任務1");
Task task2 =new Task("2","任務2");
Task task3 =new Task("3","任務3");
Task task4 =new Task("4","任務4");
Task task5 =new Task("5","任務5");
Task task6 =new Task("6","任務6");
threadPoolExecutor.execute(task1);
threadPoolExecutor.execute(task2);
threadPoolExecutor.execute(task3);
threadPoolExecutor.execute(task4);
threadPoolExecutor.execute(task5);
threadPoolExecutor.execute(task6);
//關閉線程池
threadPoolExecutor.shutdown();
}
}
運行結果:(打印順序和線程執行順序可能會有差別)
使用無界隊列時:
package ConcurrentProgramming.High.ThreadPoolExecute2;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author: zdj
* @Description: 任務 需要實現Runnable接口
* @Date: 2019年03月18日 17:03
*/
public class Task implements Runnable{
private static AtomicInteger count = new AtomicInteger(0);
public void run() {
int temp = count.incrementAndGet();
System.out.println("任務" + temp);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package ConcurrentProgramming.High.ThreadPoolExecute2;
import java.util.concurrent.*;
/**
* @Author: zdj
* @Description: 線程池中使用無界隊列
* 20個任務,線程池開啓5個線程之後,剩下的15個任務暫時放到隊列LinkedBlockingQueue中,
* 知道有空閒線程執行去執行這些任務
* @Date: 2019年03月18日 17:06
*/
public class Main {
public static void main(String[] args) throws Exception{
BlockingQueue<Runnable> blockingQueue = new LinkedBlockingQueue<Runnable>();
ExecutorService executorService = new ThreadPoolExecutor(
5,10,120L, TimeUnit.SECONDS,blockingQueue
);
for (int i = 0; i < 20; i++){
executorService.execute(new Task());
}
Thread.sleep(1000);
System.out.println("隊列大小爲:"+ blockingQueue.size());
Thread.sleep(2000);
executorService.shutdown();
}
}