一、为什么使用线程池
- 降低资源消耗,提高线程使用率,降低创建线程和销毁线程的消耗
- 提高响应速度,线程来了,直接有线程可执行,而不是创建线程在执行
- 提高线程的可管理性,线程是稀缺资源,使用线程池可以统一分配调优
二、七种线程池的核心参数
- corePoolSize :核心线程数大小
- maxinumPoolSize :最大线程数大小
- keepAliveTime:表示超出核心线程数之外的线程的空闲存活时间
- unit:表示超出核心线程数之外的线程的空闲存活时间的单位
- workQueue:阻塞队列,用于存放待执行的队列
- threadFactory :线程工厂,用来生产线程执行任务
- handler:任务拒绝策略
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
三、三种阻塞队列
1. 基于数组的先进先出队列,有界
- new ArrayBlockingQueue<>(10)
2.基于链表的先进先出队列,无界
- new LinkedBlockingQueue<>()
3.无缓冲的等待队列,无界
四、四种拒绝策略
1. 默认,队列满了丢任务抛出异常
- new ThreadPoolExecutor.AbortPolicy()
2. 队列满了丢任务不异常
- new ThreadPoolExecutor.DiscardPolicy()
3. 将最早进入队列的任务丢弃,之后再尝试加入队列
- new ThreadPoolExecutor.DiscardOldestPolicy()
4. 如果添加到线程池失败,那么当前线程会自己去执行该任务
- new ThreadPoolExecutor.CallerRunsPolicy()
五、五种常见的线程池
- Executors提供的四种线程池,但是阿里Java开发手册中强烈要求我们不允许使用Executors来创建线程池对象
ExecutorService executorService = null;
executorService = Executors.newSingleThreadExecutor();
executorService = Executors.newFixedThreadPool(10);
executorService = Executors.newCachedThreadPool();
executorService = Executors.newScheduledThreadPool();
- 通过ThreadPoolExecutor自定义创建线程池,阿里规范推荐使用自定义线程池
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(10,20 , 30,TimeUnit.SECONDS,new ArrayBlockingQueue<>(20));
}
1.阿里为什么不推荐使用Executors创建线程池呢
- newFixedThreadPool和newSingleThreadExecutor方法他们都使用了LinkedBlockingQueue的任务队列,LikedBlockingQueue的默认大小为Integer.MAX_VALUE
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
- newCachedThreadPool和 newScheduledThreadPool在Executor中定义的最大线程池大小为Integer.MAX_VALUE。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
- 通过分析源码阿里禁止使用Executors创建线程池的原因就是FixedThreadPool和SingleThreadPool的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
- CachedThreadPool和ScheduledThreadPool允许的创建最大线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM
六、线程池的执行流程
- 当任务来时,如果核心线程有空闲线程,则交给空闲线程执行
- 若核心线程池满了,将任务写入阻塞队列
- 若阻塞队列满了,如果未达到最大线程池大小,则创建临时线程,执行该任务
- 若最大线程数满了,则选择相应的拒绝策略
七、线程池的复用原理
- 线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了Thread创建线程时的一个线程必须对应一个任务的限制
- 在线程池中,同一个线程不断的从阻塞队列中获取任务,其核心原理是线程池对Thread进行了封装,并不是每次任务都会调用Thread.start()方法来创建新线程,而是让让每个线程去执行一个循环任务,不停的检查是否有新任务执行,如果有则直接执行,也就是任务中的run()方法,将run方法当成普通的方法进行执行
八、线程池的阻塞队列的作用是什么?为什么先添加到队列而不是先创建最大线程
- 阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使线程进入wait状态,释放CPU资源
- 阻塞队列自带阻塞和唤醒的功能,不需要额外处理,无任务执行时,线程池利用阻塞队列的take方法挂起,从而维持核心线程的存活,不至于一直占的CPU资源
- 在创建新线程的时候,是要获取全局锁的,这个时候其他的就得阻塞,影像了整体效率