一个线程池管理了一组工作线程,同时它还包括了一个用于放置等待执行的任务的队列。线程池可以避免线程的频繁创建与销毁,降低资源的消耗,提高系统的反应速度。java.util.concurrent.Executors提供了几个java.util.concurrent.Executor接口的实现用于创建线程池,其主要涉及四个角色:
- 线程池:Executor
- 工作线程:Worker线程,Worker的run()方法执行Job的run()方法
- 任务Job:Runable和Callable
- 阻塞队列:BlockingQueue
一、 线程池Executor
Executor及其实现类是用户级的线程调度器,也是对任务执行机制的抽象,其将任务的提交与任务的执行分离开来,核心实现类包括ThreadPoolExecutor(用来执行被提交的任务)和ScheduledThreadPoolExecutor(可以在给定的延迟后执行任务或者周期性执行任务)。
Executor的实现继承链条为:(父接口)Executor -> (子接口)ExecutorService -> (实现类)[ ThreadPoolExecutor + ScheduledThreadPoolExecutor ]。
二、任务Runable/Callable
Runnable(run)和Callable(call)都是对任务的抽象,但是Callable可以返回任务执行的结果或者抛出异常。
三、任务执行状态Future
Future是对任务执行状态和结果的抽象,核心实现类是furtureTask (所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值) ;
(1). 使用Callable+Future获取执行结果
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
Future<Integer> result = executor.submit(task);
System.out.println("task运行结果" + result.get());
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子线程在进行计算");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++)
sum += i;
return sum;
}
(2). 使用Callable + FutureTask获取执行结果
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
executor.submit(futureTask);
System.out.println("task运行结果"+futureTask.get());
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子线程在进行计算");
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++)
sum += i;
return sum;
}
四、四种常用的线程池
(1). FixedThreadPool
用于创建使用固定线程数的ThreadPool,corePoolSize = maxPoolSize = n(固定的含义),阻塞队列为LinkedBlockingQueue。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
(2). SingleThreadExecutor
用于创建一个单线程的线程池,corePoolSize = maxPoolSize = 1,阻塞队列为LinkedBlockingQueue。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
(3). CachedThreadPool
用于创建一个可缓存的线程池,corePoolSize = 0, maxPoolSize = Integer.MAX_VALUE,阻塞队列为SynchronousQueue(没有容量的阻塞队列,每个插入操作必须等待另一个线程对应的移除操作,反之亦然),是根据需求创建新线程的,需求多时,创建的就多,需求少时,JVM自己会慢慢的释放掉多余的线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
(4). ScheduledThreadPoolExecutor
用于创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
new DelayedWorkQueue());
}
五、线程池的饱和策略
当阻塞队列满了,且没有空闲的工作线程,如果继续提交任务,必须采取一种策略处理该任务,线程池提供了4种策略:
- AbortPolicy:直接抛出异常,默认策略;
- CallerRunsPolicy:用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中最老的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
当然也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
六、 线程池调优
- 设置最大线程数,防止线程资源耗尽;
- 使用有界队列,从而增加系统的稳定性和预警能力(饱和策略);
- 根据任务的性质设置线程池大小:CPU密集型任务(CPU个数个线程),IO密集型任务(CPU个数两倍的线程),混合型任务(拆分)。