文章目录
1、为什么要用线程池
1、线程的创建需要开辟虚拟机栈、本地方法栈、程序计数器等线程私有的内存空间;
2、在销毁时需要回收这些系统资源,通过复用已有线程可以更好地管理和协调线程的工作;
3、线程池主要解决两个问题:a. 当执行大量异步任务时线程池能够提供很好的性能;b. 线程池提供了一种资源限制和管理手段,比如可以限制线程的个数,动态新增线程等。
2、线程池工作原理分析
2.1 线程池工作原理
当调用execute或者submit,将一个任务提交给线程池,线程池收到这个任务请求后,有以下几种处理情况:
- 当前线程池中运行的线程数量还没有达到corePoolSize大小时,线程池会创建一个新线程执行提交的任务,无论之前创建的线程是否处于空闲状态。
- 当前线程池中运行的线程数量已经达到corePoolSize大小时,线程池会把任务加入到等待队列中,直到某一个线程空闲了,线程池会根据我们设置的等待队列规则,从队列中取出一个新的任务执行;
- 如果线程数大于corePoolSize数量但是还没有达到最大线程数maximumPoolSize,并且等待队列已满,则线程池会创建非核心线程来执行任务;
- 如果提交的任务,无法被核心线程直接执行,又无法加入等待队列,又无法创建“非核心线程”直接执行,线程池会根据拒绝处理器定义的策略处理这个任务。
- 拒绝策略类型
- AbortPolicy:默认饱和策略,丢弃任务并抛出RejectedExecutionException异常。调用者可以捕获这个异常并自行处理;
- CallerRunsPolicy:线程池中不再处理该任务,由调用线程处理该任务;
- DiscardPolicy:当任务无法添加到队列中等待执行时,DiscardPolicy策略会丢弃任务,并且不抛异常;
- DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试提交新的任务。
2.2 线程池结构
- worker集合:保存所有的核心线程和非核心线程,其本质是一个HashSet
private final HashSet<Worker> workers=new HashSet<>();
- 等待任务队列:当核心线程的个数达到corePoolSize时,新提交的任务会被先保存在等待队列中,其本质是一个阻塞队列BlockingQueue
private final BlockingQueue<Runnable> workQueue;
ThreadPoolExecutor中使用以下几种队列
- ArrayBlockingQueue:数组实现的有界阻塞队列,队列满时,后续需要提交的任务通过handler中的拒绝策略去处理;
- LinkedBlockingQueue:链表实现的阻塞队列,默认大小是Integer.MAX_VALUE(无界队列),也可以通过传入指定队列大小capacity;
- SynchronousQueue:内部并没有缓存数据,缓存的是线程。当生产者线程进行添加操作(put)时必须等待消费者线程的移除操作(take)才会返回。SynchronousQueue可以用于实现生产者与消费者的同步;
- PriorityBlockingQueue:二叉堆实现的优先级阻塞队列,传入队列的元素不能为null并且必须实现Comparable接口;
- DelayQueue:延时阻塞队列,队列元素需要实现Delayed接口。
- ctl:是一个AtomicInteger类型,二进制高3位用来标识线程池的状态,低29位用来记录池中线程的数量;
2.3 线程池的几种状态
- RUNNING:默认状态,接收新任务并处理排队任务;
- SHUTDOWN:不接受新任务,但处理排队任务,调用shutdown()会处于该状态;
- STOP:不接收新任务,也不处理排队任务,并中断正在运行的任务,调用shutdownNow()会处于该状态;
- TIDYING:所有任务都已终止,workerCount为零时,线程会转换到TIDYING状态,并将运行terminate()方法;
- TERMINATED:terminate()运行完成后线程池转为此状态。
2.4 参数分析
- corePoolSize:表示核心线程数量;
- MaximumPoolSize:表示线程池最大能够容纳同时执行的线程数,必须大于或等于1,如果和corePoolSize相等即是固定大小线程池;
- keepAliveTime:表示非核心线程存活时间,当空闲时间达到此值时,线程会被销毁直到剩下corePoolSize;
- unit:用来指定keepAliveTime的时间单位,有MILLISECONDS、SECONDS、MINUTES、HOURS等;
- workQueue:等待队列,BlockingQueue类型;当请求任务数大于corePoolSize时,任务被缓存在此BlockingQueue中;
- threadFactory:线程工厂,线程池中使用它来创建线程,如果传入的是null,则使用默认工厂类DefaultThreadFactory
- handler:执行拒绝策略的对象,当workQueue满了之后并且活动线程数大于maximumPoolSize时,线程池通过该策略处理请求;
注: 当ThreadPoolExecutor的allowCoreThreadTimeOut设置为true时,核心线程超时后也会被销毁。
3、创建线程池
3.1 newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按先进先出的顺序执行。
public class CreateSingleThreadExecutor {
public static void main(String[] args) throws InterruptedException {
//创建单线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for(int i=1;i<=5;i++){
final int taskID=i;
//向线程池提交任务
singleThreadExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程:"+Thread.currentThread().getName()
+"正在执行 task:"+taskID);
}
});
Thread.sleep(1000);
}
}
}
- 打印结果
线程:pool-1-thread-1正在执行 task:1
线程:pool-1-thread-1正在执行 task:2
线程:pool-1-thread-1正在执行 task:3
线程:pool-1-thread-1正在执行 task:4
线程:pool-1-thread-1正在执行 task:5
3.2 newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程;若无可回收,则新建线程。
public class NewCachedThreadPool {
public static void main(String[] args) throws InterruptedException {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i=1;i<=5;i++){
final int taskID=i;
newCachedThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程:"+Thread.currentThread().getName()+
"正在执行: task:"+taskID);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
newCachedThreadPool.shutdown();
}
}
- 执行结果
线程:pool-1-thread-3正在执行: task:3
线程:pool-1-thread-2正在执行: task:2
线程:pool-1-thread-1正在执行: task:1
线程:pool-1-thread-5正在执行: task:5
线程:pool-1-thread-4正在执行: task:4
- 从上面日志可以看出,缓存线程池会创建新的线程来执行任务。但是如果在提交任务前休眠1秒钟,如下:
public class NewCachedThreadPool {
public static void main(String[] args) throws InterruptedException {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
for (int i=1;i<=5;i++){
final int taskID=i;
Thread.sleep(1000);
newCachedThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程:"+Thread.currentThread().getName()+
"正在执行: task:"+taskID);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
newCachedThreadPool.shutdown();
}
}
- 执行结果
线程:pool-1-thread-1正在执行: task:1
线程:pool-1-thread-1正在执行: task:2
线程:pool-1-thread-1正在执行: task:3
线程:pool-1-thread-1正在执行: task:4
线程:pool-1-thread-1正在执行: task:5
再次执行则打印日志与SingleThreadPool一模一样,原因是提交的任务只需要500毫秒即可执行完毕,休眠1秒导致在新的任务提交之前,线程“pool-1-thread-1”已经处于空闲状态,可以被复用执行任务。
3.3 newFixedThreadPool
创建一个固定数目的、可重用的线程池。
public class NewFixedThreadPool {
public static void main(String[] args) {
//创建线程数量为3的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i=1;i<=10;i++){
final int taskID=i;
fixedThreadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程:"+Thread.currentThread().getName() +
"正在执行的task:"+taskID);
}
});
}
}
}
- 执行结果
线程:pool-1-thread-1正在执行的task:1
线程:pool-1-thread-3正在执行的task:3
线程:pool-1-thread-2正在执行的task:2
线程:pool-1-thread-1正在执行的task:4
线程:pool-1-thread-2正在执行的task:6
线程:pool-1-thread-1正在执行的task:7
线程:pool-1-thread-3正在执行的task:5
线程:pool-1-thread-1正在执行的task:9
线程:pool-1-thread-2正在执行的task:8
线程:pool-1-thread-3正在执行的task:10
3.4 newScheduledThreadPool
创建一个定时线程池,支持定时及周期性任务执行
public class NewScheduledThreadPool {
public static void main(String[] args) throws InterruptedException {
//创建一个线程数量为3的线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//延迟1s执行,每隔1秒执行一次
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Date now = new Date();
System.out.println("线程:" + Thread.currentThread().getName() + "报时" + now);
}
}, 1000, 1000, TimeUnit.MILLISECONDS);
Thread.sleep(5000);
//使用shutdown关闭定时任务
scheduledThreadPool.shutdown();
}
}
上面代码创建了一个线程数量为3的定时任务线程池,通过scheduleAtFixedRate方法,指定每隔1s执行一次任务,并且在5s之后通过shutdown方法关闭定时任务,打印结果如下:
线程:pool-1-thread-1报时Tue May 04 11:19:48 CST 2021
线程:pool-1-thread-1报时Tue May 04 11:19:49 CST 2021
线程:pool-1-thread-2报时Tue May 04 11:19:50 CST 2021
线程:pool-1-thread-2报时Tue May 04 11:19:51 CST 2021
线程:pool-1-thread-2报时Tue May 04 11:19:52 CST 2021