在juc包下有很多多线程相关的工具,其中线程池是比较常用的一个。线程池就是针对线程的一种池化技术。我们知道,线程的创建维护都是要操作系统来完成,是一件耗费资源的事情,频繁创建销毁线程将会占用较多的系统资源,影响应用程序性能。因而,线程共享成为一种需求。
在java中Thread代表线程,ThreadPoolExecutor是基础的线程池类。我们先看一下线程池类ThreadPoolExecutor的构造方法。
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
各参数解释
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:时间值
unit:时间单位
workQueue:缓存队列
threadFactory:线程工厂
handler:拒绝策略
线程池运行机制:
1、刚开始线程池为空,没有任何线程;
2、当有一个任务被提交的时候,如果线程数不大于corePoolSize,则使用线程工厂创建一个线程来处理该任务
3、当线程数已经等于corePoolSize,又有任务被提交的时候,则该任务被放到缓存队列workQueue,等待被调度。
4、当线程数等于corePoolSize且小于maximumPoolSize,缓存队列workQueue也已经满了,再有任务提交的话,则创建一个新线程用于处理该任务。5
5、当线程数等于maximumPoolSize,缓存队列workQueue也是满的,再有任务提交的话执行拒绝策略handler
6、如果任务比较少,线程池中已创建的线程会有空闲。如果线程数大于corePoolSize,且有线程空闲时间超过keepAliveTime和unit指定的时间,则该线程会被销毁,一直到线程数等于corePoolSize。当然也可以设置核心线程空闲回收。
关于拒绝策略
默认有四种,都是实现了RejectedExecutionHandler接口。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
CallerRunsPolicy:由主线程直接调用任务的run方法执行。
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
AbortPolicy :由主线程抛出异常。
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
DiscardPolicy :由主线程什么也不做,队列满加不了就加不了了,直接丢弃当前任务了。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
DiscardOldestPolicy :丢弃缓存队列头部的任务,并将当前任务重新提交。
关于线程工厂
默认使用的是DefaultThreadFactory,其核心方法是newThread,为一个任务Runnable创建一个Thread。
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
里面做了很少的变动,跟我们自己new的比起来定制的东西挺少的,我们通常使用默认的线程工厂即可。
关于Executors中的线程池
因为一般是对ThreadPoolExecutor的定制,所以我们只要对ThreadPoolExecutor足够清楚就行。Executors中的线程池在生产上不见得适用。例如固定线程数的线程池,相关代码如下:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<runnable>());/<runnable>
}
由于缓存队列没有容量限制,在高并发情况下,很容易占用大量内存导致OOM,所以阿里巴巴开发手册明确禁止使用Executors中封装的线程池,要求程序开发者自己new ThreadPoolExecutor。
最后,一个面试中遇到的问题,我也不知道答案:线程池为什么要按顺序“创建达到corePoolSize、任务放缓存队列、创建达到maximumPoolSize”?如果后两步交换一下行不行?为什么这么设计?
谁理解这个设计的请积极回复,感谢。
閱讀更多 道法自然理在悟心 的文章