ThreadPoolExecutor类详解
使用线程池的目的是:如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。所以需要统一管理
1.构造和参数
在ThreadPoolExecutor类中四个构造方法:
前三个都是调第四个构造方法,参数也最多如下
-
corePoolSize:核心线程数,会一直存活,即使没有任务,线程池也会维护线程的最少数量
-
maximumPoolSize:线程池维护线程的最大数量
-
keepAliveTime:线程池维护线程所允许的空闲时间,当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。
-
unit:线程池维护线程所允许的空闲时间的单位,参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
-
workQueue:一个阻塞队列,用来存储等待执行的任务,常用队列如下
ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue;
-
threadFactory:线程工厂,主要用来创建线程
-
handler:线程池中的数量大于maximumPoolSize,对拒绝任务的处理策略,取值如下
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
2.调用关系
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法
ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等
Executor是顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,从字面意思可以理解,就是用来执行传进去的任务的
3.ThreadPoolExecutor类中重要方法
- execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
- submit()用来向线程池提交任务,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用Future来获取任务执行结果
- shutdown():不会立即关闭线程池,但也不接受新的任务,等待队列中所有任务执行完毕后关闭。
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,清空工作队列,返回尚未执行的任务。
- 其他的方法:getQueue(),getPoolSize(),getActiveCount(),getCompletedTaskCount()等获取与线程池相关属性的方法
线程池实现原理
线程池状态
Worker
实现runnable,继承aqs,处理线程调度用的,具体源码自己去看比较多
execute()源码
//通过execute向线程池提交任务public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get(); //如果当前线程数未达到核心线程数,则直接创建线程来执行新任务
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
} //否则将任务加入阻塞队列,这里进行双重检查,如果线程池已经关闭,则调用reject(), //如果当前线程池线程数为0,则新建线程
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
} //如果加入阻塞队列失败,则尝试新建一个线程,如果失败了 //则说明线程池关闭了或者线程达到最大线程数,因此调用reject()
else if (!addWorker(command, false))
reject(command);
}
简单实例
public class Test {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyTask myTask = new MyTask(i);
executor.execute(myTask);
System.out.println("线程池中线程数目:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
}
executor.shutdown();
}
}
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
@Override
public void run() {
System.out.println("正在执行task "+taskNum);
try {
Thread.currentThread().sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"执行完毕");
}
}
结果
当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程
使用线程池实现异步工具
@Component
@Slf4j
public class AsyncUtils {
private static final ExecutorService EXECUTOR = new ThreadPoolExecutor(1, 5, 60,
TimeUnit.MINUTES, new ArrayBlockingQueue<>(512));
@Resource
private AsyncTaskMapper asyncTaskMapper;
public void execute(Runnable runnable, String param, String taskDesc) {
AsyncTaskDO asyncTaskDO = AsyncTaskDO.builder()
.param(param)
.taskDesc(taskDesc)
.build();
asyncTaskMapper.insert(asyncTaskDO);
EXECUTOR.submit(new TenantRunnableWrap(runnable, asyncTaskMapper, asyncTaskDO));
}
@Slf4j
static class TenantRunnableWrap implements Runnable {
private final Runnable task;
private final AsyncTaskMapper asyncTaskMapper;
private final AsyncTaskDO asyncTaskDO;
public TenantRunnableWrap(Runnable task, AsyncTaskMapper asyncTaskMapper, AsyncTaskDO asyncTaskDO) {
this.task = task;
this.asyncTaskMapper = asyncTaskMapper;
this.asyncTaskDO = asyncTaskDO;
}
@Override
public void run() {
try {
task.run();
} catch (Exception e) {
asyncTaskDO.setExceptionDesc(e.getMessage());
asyncTaskMapper.updateById(asyncTaskDO);
return;
}
asyncTaskMapper.deleteById(asyncTaskDO);
}
}
}
使用方法如下,传参的目的是统计出异常的时候存表,对于异步任务不好解决报错提供了一个思路
如何合理配置线程池的大小
阿里巴巴Java开发手册中明确指出,而且用的词是『不允许』使用Executors创建线程池:
不允许使用Executors创建线程池
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
Positive example 1:
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
Positive example 2:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
Positive example 3:
<bean id="userThreadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="2000" />
<property name="threadFactory" value= threadFactory />
<property name="rejectedExecutionHandler">
<ref local="rejectedExecutionHandler" />
</property>
</bean>
//in code
userThreadPool.execute(thread);
参考:
Java并发编程:线程池的使用