一、为什么要使用线程池,线程池有什么用?
- 降低资源消耗:通过重用已经创建的线程来降低线程创建和销毁的消耗
- 提高响应速度:任务到达时不需要等待线程创建就可以立即执行
- 提高线程的可管理性:线程池可以统一管理、分配、调优和监控
二、 Java中创建线程池有以下的方式:
1、使用ThreadPoolExecutor类(java.util.concurrent包)
//创建ThreadPoolExecutor线程池对象
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,keepAliveTime,milliseconds,runnableTaskQueue, threadFactory,handler);
2、使用Executors类创建四种常见线程池
- newCachedThreadPool (可缓存线程的线程池)创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。即根据需要创建新线程的线程池
- newFixedThreadPool(固定大小线程池) 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。即创建一个固定数量线程的线程池
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor (单线程线程池)创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。即创建只包含一个线程的线程池。
三、线程池常用参数:
1.corePoolSize 线程池核心线程大小
核心线程会一直存在,即使没有任务执行;
当线程数小于核心线程数的时候,即使有空闲线程,也会一直创建线程直到达到核心线程数;
设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭。
2.maximumPoolSize 线程池最大线程数量
线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
3.keepAliveTime 空闲线程存活时间
一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指 定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定
4.unit 空闲线程存活时间单位
5.workQueue 工作队列
新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。是指被提交但未被执行的任务所存放的队列容器
- 直接提交队列:SynchronousQueue是没有容量的容器,每一个插入的操作都需要等待相应的删除操作,SynchronousQueue不保存任务,它总是马上将任务提交给线程执行,如果没有空闲的线程则会尝试创建新的线程,如果线程数量已经达到最大值,则执行拒绝策略,使用SynchronousQueue通常需要设置很大的maximumPoolSize。
- 有界的任务队列: 当使用有界队列并有新任务时,若然线程池线程数量小于corePoolSize则会创建线程,若然大于corePoolSize则会将新任务加入任务队列,当任务队列已满无法加入时,则在总线程数不大于maximumPoolSize的前提下创建线程,若大于maximumPoolSize则执行拒绝策略,使用有界队列除非系统非常繁忙,否则确保核心线程数在corePoolSize
- 无界的任务队列: LinkedBlockingQueue 若任务创建和处理速度差异很大,无界队列会快速膨胀导致系统资源耗尽
- 优先任务队列: PriorityBlockingQueue是一个特殊的无界队列,创建PriorityBlockingQueue时可以传入Comparator对任务进行优先级处理
6.threadFactory 线程工厂
创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等。
7.handler 拒绝策略
当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略:
- AbortPolicy: 丢弃任务并抛出RejectedExecutionException 异常。 (默认)
- DiscardPolicy:也是丢弃任务,但是不抛出异常。
- DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
- CallerRunsPolicy:由调用线程处理该任务
四、执行过程
1.当线程数小于核心线程数时,创建线程。
2.当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
线程调用runWoker,会while循环调用getTask方法从workerQueue里读取任务,然后执行任务。只要 getTask方法不返回null,此线程就不会退出。
3.当线程数大于等于核心线程数,且任务队列已满。(1)若线程数小于最大线程数,创建线程。(2)若线程数等于最大线程数,抛出异常,拒绝任务。
五、shutdown与shutdownNow 的区别?
调用shutdown,池中状态立马变成SHUTDOWN状态,此时再往池中添加任务,会触发拒绝策略。此时池中不会立刻退出,直到池中的任务都已经完成,才会退出,平缓的关闭。调用shutdown方法,将线程池中的空闲线程回收。该方法会使得keepAliveTime参数失效
调用shutdownNow,池中状态立马变成STOP状态,并试图停止所有正在执行的线程(除非有if判断人为的抛出异常),不再处理还在池队列中等待的任务,会返还未执行的任务。暴力关闭
六、如何判断线程池中线程全部执行完毕?
awaitTermination方法:接收人timeout和TimeUnit两个参数,用于设定超时时间及单位。当等待超过设定时间时,会监测ExecutorService是否已经关闭,若关闭则返回true,否则返回false。一般情况下会和shutdown方法组合使用
//等待所有线程执行完毕当前任务。
threadPool.shutdown();
boolean loop = true;
do {
//等待所有线程执行完毕当前任务结束
loop = !threadPool.awaitTermination(2, TimeUnit.SECONDS);//等待2秒
} while (loop);
if (loop != true) {
System.out.println("所有线程执行完毕");
}
七、简单实例
1.使用ThreadPoolExecutor类创建
public class TestThreadPoolExecutor {
public static void main(String[] args) {
long currentTimeMillis = System.currentTimeMillis();//计算耗时
// 构造一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 6, 3,TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3));
for (int i = 1; i <= 10; i++) {//任务数量。具体项目可能是批量过来的数据
try {
String task = "task=" + i;
System.out.println("创建任务并提交到线程池中:" + task);
threadPool.execute(new ThreadPoolTask(task)); //提交
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
try {
//等待所有线程执行完毕当前任务。
threadPool.shutdown();
boolean loop = true;
do {
//等待所有线程执行完毕当前任务结束
loop = !threadPool.awaitTermination(2, TimeUnit.SECONDS);//等待2秒
} while (loop);
if (loop != true) {
System.out.println("所有线程执行完毕");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("耗时:" + (System.currentTimeMillis() - currentTimeMillis));
}
}
}
public class ThreadPoolTask implements Runnable, Serializable {
private Object attachData;
ThreadPoolTask(Object tasks) {
this.attachData = tasks;
}
public void run() {//执行逻辑
try {
System.out.println("开始执行任务:" + attachData + "任务,使用的线程池,线程名称:" + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
attachData = null;
}
}
2.使用Executors类创建
public class testThreadPool {
public static void main(String[] args) {
long currentTimeMillis = System.currentTimeMillis();//计算耗时
ExecutorService executorPool=Executors.newFixedThreadPool(10);
for (int i = 1; i <=100; i++) {
String task = "task=" + i;
System.out.println("创建任务并提交到线程池中:" + task);
executorPool.execute((Runnable) new testThreadPoolTask(task));
}
try {
//等待所有线程执行完毕当前任务。
executorPool.shutdown();
boolean loop = true;
do {
//等待所有线程执行完毕当前任务结束
loop = !executorPool.awaitTermination(2, TimeUnit.SECONDS);//等待2秒
} while (loop);
if (loop != true) {
System.out.println("所有线程执行完毕");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("耗时:" + (System.currentTimeMillis() - currentTimeMillis));
}
}
}
public class testThreadPoolTask implements Runnable{
private Object attachData;
testThreadPoolTask(Object tasks) { //获取传递的参数
this.attachData = tasks;
}
@Override
public void run() {
try {
System.out.println("开始执行任务:" + attachData + "任务,使用的线程池,线程名称:" + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
attachData = null;
}
}