线程池的使用
线程池基本使用
初始化线程的4种方式
- 继承Thread
- 实现Runnable接口
- 实现Callable接口 + FutureTask (可以拿到返回结果,可以处理异常)
- 线程池
方式1和方式2:主进程无法获取线程的运算结果。不适合当前场景
方式3:主进程可以获取线程的运算结果,并设置给itemVO,但是不利于控制服务器中的线程资源。可以导致服务器资源耗尽。
方式4:通过如下两种方式初始化线程池:
Executors.newFiexedThreadPool(3);
//或者
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit unit, workQueue, threadFactory, handler);
不建议使用第一种方式初始化线程池
线程池构造参数介绍
corePoolSize
核心线程数,表示在线程池初始化过程中一直存在的线程数
maximumPoolSize
最大线程数,表示一个线程池创建过程中最大拥有的线程的数量
keepAliveTime
线程空闲时存活时间,表示一个线程在空闲的时候如果超过空闲时存活时间,那么这个线程有可能被销毁(核心线程除外)
unit
顾名思义,表示空闲时存活时间的单位
workQueue
任务队列。被提交但尚未执行的任务存放在这里。
threadFactory
线程工厂。用于生成线程池中工作线程的工厂,用于创建线程,一般使用默认的即可
handler
拒绝策略。当队列满了,并且工作线程已经达到maximumPoolSize时,如何拒绝请求执行的Runnable的策略。
拒绝策略
AbortPolicy
当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常。
CallerRunsPolicy
当任务添加到线程池中被拒绝时,会在线程池当前正在运行的Thread线程池中处理被拒绝的任务。
DiscardOldestPolicy
当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务,然后将被拒绝的任务添加到等待队列中。
DiscardPolicy
当任务添加到线程池中被拒绝时,线程池将丢弃被拒绝的任务。
为什么阿里开发手册不允许使用常见线程池
我们先分析一下以上几种创建线程池的源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
//newSingleThreadScheduledExecutor稍有不同,其创建线程池的核心方法如下:
public ScheduledThreadPoolExecutor(int corePoolSize) {
new ThreadPoolExecutor(1, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
不难看出核心为new ThreadPoolExecutor(…)方法。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
......
}
FixedThreadPool
对于FixedThreadPool定长线程池,我们来看一看它的源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到,定长线程池的核心线程数和最大线程数是相同的,在并发量较高的情况下,如果线程数设置的比较小的话,那么会有大量的任务进入队列中。FixedThreadPool使用的等待队列源码如下:
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
也就是说,FixedThreadPool使用了一个容量为Integer.MAX_VALUE的队列,可是int的最大值为2^31-1,一般情况下根本无法容纳这么对任务对象,所以当任务量过大的时候,使用FixedThreadPool很容易发生OOM,内存溢出。
SingleThreadExecutor
我们再来分析一下SingleThreadExecutor单一线程池的源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
单一线程池只创建了一个线程,且使用的队列与定长线程池相同,易发生同样的问题。
CacheThreadPool
再来分析一下CacheThreadPool可缓存线程池的源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
与前两种线程池不同的是,可缓存线程池的核心线程数为0,最大线程数为int类型的最大值。显然我们无法创建这么大数量的线程,在并发任务量较大的情况下容易发生内存溢出。而且核心线程池为0,可能会导致频繁创建和销毁线程,增加了系统的开销。
ScheduledThreadPool
可延迟线程池ScheduledThreadPool:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
可以看到,可延迟线程池的最大线程数为int类型的最大值,与可缓存线程池同样会发生内存溢出的问题。
综上所述,JDK为我们创建好的Executors类在真正的生产环境开发中,我们是不去使用的,容易引发内存溢出,导致程序崩溃。