线程池
基本概念: 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。
不要使用Executor创建线程池,而是通过ThreadPoolExecutor的方式创建,这样的处理方式能让编写代码的人更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允许请求的队列长度为Interger.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:
允许的创建线程数量为:Interger.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
Executor创建线程的3大方法:
//Executor不建议使用,容易引发OOM
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单线程
ExecutorService threadPool1 = Executors.newFixedThreadPool(5);//固定线程
ExecutorService threadPool2 = Executors.newCachedThreadPool();//可变线程
for (int i = 0; i < 10; i++) {
threadPool2.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
ThreadPoolExecutor的七大参数:
核心线程数,最大线程数,超时等待时间,超时等待单位,阻塞队列,线程工厂,拒绝策略。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
执行过程:如果线程数没有超过核心线程数,那么就会用到几个开辟几个,如果大于核心线程数,但是没有超过阻塞队列的个数,那么就会等待核心线程执行完,再取阻塞队列中的执行
测试代码:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
2,
6,
3,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 0; i < 7; i++) {
final int temp = i;
threadPoolExecutor.execute(()->
{
try {
System.out.println(Thread.currentThread().getName()+" "+temp);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
调整不同的循环数会出现不同的执行顺序,甚至会报错,具体是什么原理呢?下面用画图的方式描述出来。
执行过程
以核心线程数为2,最大线程数为6,阻塞队列容量为3 为例:
- 如果要执行的任务数小于等于核心线程数:
如图,蓝色为待执行的任务,黄色为核心线程,当要执行的任务数小于等于核心线程数时,直接被核心线程接管:
- 当待执行的任务数大于核心线程数,但是小于核心线程数+阻塞队列容量时:
task1,task2被核心线程接管,task3进阻塞队列等待核心线程执行完毕再执行。
- 当待执行的任务数大于核心线程数+阻塞队列容量但小于最大线程数+阻塞队列容量时:
因为阻塞队列已达到最大容量,剩余的线程(除去核心线程的其他线程)会被启动,用来直接接管新来的任务,当线程经过keepAliveTime
还没有接到任务,则再次关闭线程。
注意:直接接管新来的任务,而不是从阻塞队列中取
- 当待执行的任务数大于了最大线程数+阻塞队列容量时:
超出数量的任务会被拒绝,具体的拒绝策略有四种:
ThreadPoolExecutor.AbortPolicy()
:该策略是指,当有超出数量的任务来时,会抛出异常。
ThreadPoolExecutor.CallerRunsPolicy();
:超出数量的任务会被调用者线程执行,比如:
因为是主线程调用的,所以会被主线程接管。
ThreadPoolExecutor.DiscardPolicy();
:当超出数量的任务来时,不会抛出异常。
可以看出当循环次数为10时,只执行了9个,多出的任务被丢弃。
ThreadPoolExecutor.DiscardOldestPolicy();
:超出数量的任务会尝试去和最早执行的线程竞争。