优势
- 构建一个新的线程是有一定代价的, 因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池
- 减少并发线程的数目
Executors
执行器( Executors) 类有许多静态工厂方法用来构建线程池
方法 | 描述 |
newCachedThreadPool | 必要时创建新线程;空闲线程会被保留 60 秒 |
newFixedThreadPool | 该池包含固定数量的线程;空闲线程会一直被保留 |
newSingleThreadExecutor | 只有一个线程的 “ 池”, 该线程顺序执行每一个提交的任务(类似于 Swing 事件分配线程) |
newScheduledThreadPool | 用于预定执行而构建的固定线程池, 替代 java.util.Timer |
newSingleThreadScheduledExecutor | 用于预定执行而构建的单线程 “ 池” |
newWorkStealingPool (1.8新增) | 创建一个线程池,该线程池维护足够的线程以支持给定的并行级别,并且可以使用多个队列来减少争用。并行级别对应于主动参与或可用于进行任务处理的最大线程数。线程的实际数量可以动态地增长和收缩。工作窃取池不能保证执行提交的任务的顺序。 |
重点介绍下列三种
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
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>()));
}
- newCachedThreadPool 方法构建了一个线程池,初始线程数0,对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有可用的空闲线程,则创建一个新线程。60秒后回收不使用的线程。
- newFixedThreadPool 方法构建一个具有固定大小的线程池。 如果提交的任务数多于空闲的线程数,那么把得不到服务的任务放置到队列中。当其他任务完成以后再运行它们。线程使用完立即回收。
- newSingleThreadExecutor 是一个退化了大小为 1 的线程池: 由一个线程执行提交的任务,一个接着一个。
这3个方法返回实现了ExecutorService 接口的ThreadPoolExecutor 类的对象。再将一个 Runnable 对象或 Callable 对象提交给 ExecutorService得到Future
ExecutorService service = Executors.newFixedThreadPool(10);
Callable<Integer> callable = new Callable<Integer>(){
@Override
public Integer call() throws Exception {
return 1;
}
};
Future<Integer> future = service.submit(callable);
int i = 0;
try {
i = future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println(i);
ExecutorService
- Future<?> submit(Runnable task) 使用这样一个对象来调用 isDone、 cancel 或 isCancelled。但是, get 方法在完成的时候只是简单地返回 null。
- Future<T> submit(Runnable task, T result) Future 的 get 方法在完成的时候返回指定的 result 对象。
- Future<T> submit(Callable<T> task) 返回的 Future 对象将在计算结果准备好的时候得到它。
- void shutdown() 关闭服务, 会先完成已经提交的任务而不再接收新的任务。
ThreadPoolExecutor
使用ThreadPoolExecutor对象构造线程池。
我们观察Executors的newCachedThreadPool源码,内部实例化了一个ThreadPoolExecutor对象。
观察ThreadPoolExecutor构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
从结构关系上分析ThreadPoolExecutor继承了AbstractExecutorService抽象类,并实现了ExecutorService接口
从构造参数上分析
- corePoolSize: corePoolSize线程池中要保持的数量,即使它们是空闲的,除非设置了allowCoreThreadTimeOut属性为true
- maximumPoolSize:最大允许在池中允许的最大线程数。
- keepAliveTime:当线程的数量大于核心时,这是空闲线程在终止之前等待新任务的最大时间。(超过核心线程数的线程如果长时间获得不到新任务就会终止掉)
- unit : keepAliveTime的单位 如TimeUnit.SECONDS 代表秒
- workQueue:工作队列(实现BlockingQueue接口的阻塞队列)用于在执行任务之前保存任务的队列。此队列只保留execute方法提交的Runnable任务。常使用的阻塞队列LinkedBlockingQueue、SynchronousQueue、ArrayBlockingQueue。使用SynchronousQueue队列表示,没有缓存队列在存储多余的线程。
线程的排队策略与阻塞队列相关:
LinkedBlockingQueue:当任务数大于核心线程数时,新任务添加到队列中,当队列容量达到最大时,将创建新线程,直到达到最大线程数maximumPoolSize。(基于链表实现的一个阻塞队列,默认容器大小Integer.MAX_VALUE。)
SynchronousQueue: 当任务数大于核心线程数时,直接创建新的线程直到达到最大线程数maximumPoolSize。(一个没有内部容量的阻塞队列,队列中每个元素的插入操作必须等待另一个线程执行相应的移除操作。可以理解为没有缓存)
ArrayBlockingQueue : 同LinkedBlockingQueue。(基于数组实现的一个阻塞队列,需要指定容器大小。)
- threadFactory:创建新线程时要使用的工厂。
- handler : 处理程序在执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量。
handler有以下四种策略:
ThreadPoolExecutor.AbortPolicy():丢弃任务并抛出RejectedExecutionException异常。 (ThreadPoolExecutor默认)
ThreadPoolExecutor.CallerRunsPolicy():由调用线程处理该任务 。
ThreadPoolExecutor.DiscardPolicy():也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
小结
使用ThreadPoolExecutor构造方法去生成线程池更加灵活,我们可以灵活指定核心线程数,最大线程数,工作队列及空闲线程保存时间。
构造一个核心线程数为5,最大线程数为10,缓存任务数为5,线程超时为2秒的线程池。
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 10, 2000, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(5));