无界队列
**newFixedThreadPool和newSingleThreadExecutor在默认情况下将使用一个无界的队列(LinkedBlockingQueue),**如果所有线程都在执行任务,那么任务将在队列中等待,如果任务到达的速度大于线程执行的速度,造成的后果将是队列无限期增加。
使用无界队列时注意OOM,可能导致CPU和内存飙升服务器挂掉。
有界队列
更稳妥的管理策略是使用有界队列,如:ArrayBlockingQueue,有界的LinkedBlockingQueue,PriorityBlockingQueue. PriorityBlockingQueue中的优先级由任务的Comparator决定。
使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
有界队列避免了资源耗尽的情况,但出现一个问题,队列填满后,新的任务该怎么办?使用拒绝策略。
同步移交
如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。
线程池的拒绝策略
JDK提供了几种不同的RejectedExecutionHandler实现,每种都是不同的饱和策略:AbortPolicy,CallerRunsPolicy,DiscardPolicy和DiscardOldestPolicy.
四种策略都做为静态内部类在ThreadPoolExcutor中进行实现。
- AbortPolicy 终止策略。当任务添加到线程池中被拒绝时,它将抛出 RejectedExecutionException 异常(继承自RuntimeException),调用者可以捕获该异常自行处理。是默认饱和策略。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
- DiscardOldestPolicy 抛弃策略:当任务添加到线程池中被拒绝时,线程池会放弃等待队列中最旧的未处理任务(抛弃下一个将被执行的任务),然后将被拒绝的任务添加到等待队列中,如果队列是一个优先队列,那么抛弃最旧的策略就会抛弃优先级最高的任务,因此不要将两者在一起使用。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
- DiscardPolicy 该策略默默地丢弃无法处理的任务,不予任何处理。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
- CallerRunsPolicy 该策略只要线程池未关闭,该策略直接在调用者线程(主线程)中,运行当前被丢弃的任务(白话就是不会抛弃线程,也不抛出异常,而是将任务回退到调用者,从而降低新任务的流量),这样会影响QPS(Queries per second)。
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
QPS:Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。
还可以自定义饱和策略。
java提供的四种常用线程池解析
1.newCachedThreadPool
在newCachedThreadPool中如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在无界队列中等待。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
构造函数:
public ScheduledThreadPoolExecutor(int corePoolSize) {
//继承自 ThreadPoolExecutor
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
DelayedWorkQueue是一个无界队列,它能按一定的顺序对工作队列中的元素进行排列。在这里,作为静态内部类就在ScheduledThreadPoolExecutor中进行了实现。
4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
首先new了一个线程数目为1的ScheduledThreadPoolExecutor,再把该对象传入DelegatedScheduledExecutorService中,看看DelegatedScheduledExecutorService的实现代码:
DelegatedScheduledExecutorService(ScheduledExecutorService executor) {
super(executor);
e = executor;
}
父类:
DelegatedExecutorService(ExecutorService executor) { e = executor; }
其实就是使用装饰模式增强了ScheduledExecutorService(1)的功能,不仅确保只有一个线程顺序执行任务,也保证线程意外终止后会重新创建一个线程继续执行任务。具体实现原理会在后续博客中讲解。
5.newWorkStealingPool创建一个拥有多个任务队列(以便减少连接数)的线程池。
这是jdk1.8中新增加的一种线程池实现,先看一下它的无参实现:
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool
(Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null, true);
}
返回的ForkJoinPool从jdk1.7开始引进,个人感觉类似于mapreduce的思想。