引言
合理利用线程池能够带来三个好处:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
本篇参考:http://www.infoq.com/cn/articles/java-threadPool
http://www.cjsdn.net/Doc/JDK60/
关于线程池的配置参数
Java中创建线程池创建如下:
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
ThreadPoolExecutor的构造函数有多个,通过构造函数的参数配置线程池,以下是各个参数的说明:
corePoolSize:线程池的基本大小,maximumPoolSize:线程池最大大小,即线程池允许创建的最大线程数,线程池会根据corePoolSize(调用getCorePoolSize())和maximumPoolSize(调用getMaximumPoolSize())去自动调整池中的线程数量(调用getPoolSize())。当池中的线程数少于corePoolSize的时候,会创建新的线程放到任务队列处理请求,不管队列中其他的线程是否空闲;当线程数大于coolPoolSize而小于maximumPoolSize的时候,如果池中任务队列为空,则创建线程放入任务队列。当设置maximumPoolSize为Integer.MAX_VALUE,则这个线程池为无界线程池,然后池中的线程数量是任意的。如果任务队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务,该线程等待进入任务队列。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。(鄙人估计corePoolSize确定了任务队列的大小)
workQueue:任务队列,用于转移和阻塞提交了的任务,即任务队列是运行线程的,任务队列根据corePoolSize和maximumPoolSize工作:
1.当正在运行的线程小于coolPoolSize,线程池会创建新的线程。
2.当大于coolPoolSize而任务队列未满,则从队列里拿一个空闲的线程去接任务。
3.当大于coolPoolSize而任务队列满了(即队列中没有空闲的线程),并且小于maximumPoolSize,会创建新的线程接任务,该线程等待进入任务队列。
4.当大于maximumPoolSize,该任务会根据handler(RejectedExecutionHandler,饱和策略)处理。
参考图:
任务队列又有以下集中策略模式:
1.直接提交。工作队列的默认选项是 SynchronousQueue
,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
2.无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue
)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue
)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
handler:RejectedExecutionHandler(饱和策略),当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。
1.在默认的 ThreadPoolExecutor.AbortPolicy
中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException
。
2.在 ThreadPoolExecutor.CallerRunsPolicy
中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
3.在 ThreadPoolExecutor.DiscardPolicy
中,不能执行的任务将被删除。
4.在 ThreadPoolExecutor.DiscardOldestPolicy
中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。
定义和使用其他种类的
RejectedExecutionHandler
类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。
keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。
unit(线程活动保持时间的单位):可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
threadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。如果没有另外说明,则在同一个
ThreadGroup
中一律使用
Executors.defaultThreadFactory()
创建线程,并且这些线程具有相同的
NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从
newThread 返回 null 时
ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。
钩子 (hook) 方法
此类提供
protected 可重写的
beforeExecute(java.lang.Thread, java.lang.Runnable)
和
afterExecute(java.lang.Runnable, java.lang.Throwable)
方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化 ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法
terminated()
来执行 Executor 完全终止后需要完成的所有特殊处理。 如果钩子 (hook) 或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。队列维护 方法
getQueue()
允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。
remove(java.lang.Runnable)
和
purge()
这两种方法可用于在取消大量已排队任务时帮助进行存储回收。
线程池的关闭
我们可以通过调用线程池的shutdown或shutdownNow方法来关闭线程池,它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。只要调用了这两个关闭方法的其中一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow。
总结
举个不太优雅的例子,将线程池比喻成一个窑子,每个任务都是嫖客,线程就是接待客人的妹子,一个任务一条线程(不搞多P),corePoolSize是坐台的妹子数,maximumPoolSize是妹子的总数,任务队列是排着正在ooxx的以及准备ooxx的其大小为一次最多只允许有多少对在ooxx,keepAliveTime是每个妹子最大工作时长,做完若没超过时长就是闲着的,从闲着的坐台妹子选出再接另外的,若没闲着的坐台妹子,再叫人数过来补充,但不能超过妹子总数,超过了的话按经营策略(
RejectedExecutionHandler——策略
)处理,
所以可以想象,窑子老板就想多赚,坐台的妹子只要还行,接着来,不会轻易给你个新鲜的
。
因为有任务队列大小限制(房间有限,允许ooxx的对数不能太多),若队列满了,生意好,客人只能携着妹子等了。。。。大概意思是如此,给个图自己脑补一下:
以下是示例代码:
ThreadPoolGenerator.java,封装了ThreadPoolExecutor的创建:
package testThreadPool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolGenerator {
private volatile static ThreadPoolExecutor threadPoolExecutor;
//写了自己的一个Executor,主要是用于线程池执行完所有任务所需的时间,重写了terminated方法
public static class MyThreadPoolExecutor extends ThreadPoolExecutor {
long sTime = 0L;
long eTime = 0L;
public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, handler);
}
@Override
protected void terminated() {
super.terminated();
if(sTime != 0l) {
eTime = System.currentTimeMillis();
System.out.println("executor completed tasks take: " + (eTime - sTime) + " ms");
}
}
//记录Executor开始执行的那刻的时间
public void startLogTime() {
sTime = System.currentTimeMillis();
}
}
public static ThreadPoolExecutor getThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
Long keepAliveTime, int quequeSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
if(threadPoolExecutor == null) {
synchronized (ThreadPoolGenerator.class) {
if(threadPoolExecutor == null) {
threadFactory = threadFactory == null ? Executors.defaultThreadFactory() : threadFactory;
handler = handler == null ? new ThreadPoolExecutor.AbortPolicy() : handler;
threadPoolExecutor = new MyThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(quequeSize), threadFactory, handler);
}
}
}
return threadPoolExecutor;
}
}
TestTask.java, 一个测试任务类:
package testThreadPool;
public class TestTask implements Runnable{
private String name;
public TestTask(String name) {
this.name = name;
}
//模拟任务,此处两个for 循环,若循环次数不大,那么有线程执行跟没线程执行效果差不多的
public void performTask() {
for(int i=0; i<100000000; i++){
for(int j=0; j<1000000; j++) {
j=i+j;
}
}
System.out.println(name + " completed.");
}
//通过线程执行模拟任务
@Override
public void run() {
this.performTask();
}
}
TestCase.java,测试类:
package testThreadPool;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import testThreadPool.ThreadPoolGenerator.MyThreadPoolExecutor;
public class TestCase {
public static void main(String[] args) {
List<TestTask> tasks = new ArrayList<TestTask>();
for(int i=1; i<=5; i++)
tasks.add(new TestTask("Task " + i));
TestCase c = new TestCase();
System.out.println("no thread case:");
long sTime = System.currentTimeMillis();
c.noThreadProcess(tasks);
long eTime = System.currentTimeMillis();
long tTime = eTime - sTime;
System.out.println("process take :" + tTime + "ms");
System.out.println("=========================");
System.out.println();
System.out.println("with thread case:");
ThreadPoolExecutor executor = ThreadPoolGenerator.getThreadPoolExecutor(3, 5, 5000L, 3, null, null);
c.withThreadProcess(executor, tasks);
eTime = System.currentTimeMillis();
System.out.println("=========================");
}
public void withThreadProcess(ThreadPoolExecutor executor, List<TestTask> list) {
//此举用来启动记录执行耗时
((MyThreadPoolExecutor)executor).startLogTime();
for(TestTask task : list) {
executor.execute(task);
}
executor.shutdown();
}
public void noThreadProcess(List<TestTask> list) {
for(TestTask task : list) {
task.performTask();
}
}
}
测试结果:
no thread case:
Task 1 completed.
Task 2 completed.
Task 3 completed.
Task 4 completed.
Task 5 completed.
process take :2802ms
=========================
with thread case:
=========================
Task 3 completed.
Task 2 completed.
Task 1 completed.
Task 4 completed.
Task 5 completed.
executor completed tasks take: 1224 ms
线程池另外一种用法,用线程池执行任务,完成后的取得执行结果:
TestMessageTask.java,那个badTask如果为true,表示为坏任务,模拟任务执行出错:
package testThreadPool;
import java.util.concurrent.Callable;
public class TestMessageTask implements Callable<String> {
private String name;
private boolean badTask;
public TestMessageTask(String name, boolean badTask) {
this.name = name;
this.badTask = badTask;
}
@Override
public String call() throws Exception {
for(int i=0; i<100000000; i++){
for(int j=0; j<1000000; j++) {
j=i+j;
}
}
//用了一个boolean,模拟执行出错的情形
if(badTask) {
throw new RuntimeException(name + " is bad task!!");
}
return name + " completed.";
}
}
MessageThreadPoolExecutor.java:
package testThreadPool;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class MessageThreadPoolExecutor extends ThreadPoolExecutor{
//把父类的构造函数全弄出来算了。。。
public MessageThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public MessageThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory);
}
public MessageThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public MessageThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, handler);
}
//定义了一个方法执行任务集合,用了泛型,表示结果类型由Callable类型的任务对象决定
public <T> Map<String, T> performTasks(Map<String, Callable<T>> taskMap) throws InterruptedException{
if(taskMap == null || taskMap.isEmpty())
throw new NullPointerException();
Map<String, Future<T>> futureMap = new HashMap<String, Future<T>>();
Map<String, T> messageMap = new HashMap<String, T>();
boolean done = false;
try {
for(String key : taskMap.keySet()) {
/**
* 这里用了两种方式执行任务:execute与submit,建议翻翻API文档,
* 关于Future的get方法,没弄懂。。。
*/
//execute 方式
/*RunnableFuture<T> futureTask = new FutureTask<T>(taskMap.get(key));
execute(futureTask);
futureMap.put(key, futureTask);*/
//submit 方式
futureMap.put(key, submit(taskMap.get(key)));
}
/**
* 再次遍历任务,逐个调用get方法,get方法会阻塞住直到任务完成,
* get方法返回一个结果,根据结果判断任务执行成功与否,这也是我没有看懂
* API的地方,那个submit方法明明说返回的Future对象如果成功它的get方法
* 返回null,但messageMap中的value是有的,不为null。。。
*/
for(String key : futureMap.keySet()) {
Future<T> f = futureMap.get(key);
try {
T result = f.get();
messageMap.put(key, result);
} catch (ExecutionException e) {
System.out.println(e.getMessage());
}
}
done = true;
return messageMap;
}finally {
//若上面出了异常没done,没做完的任务直接cancel
if(!done) {
for(String key : futureMap.keySet()) {
futureMap.get(key).cancel(true);
}
}
this.shutdown();
}
}
}
测试代码,MsgTestCase.java:
package testThreadPool;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
public class MsgTestCase {
public static void main(String[] args) {
Map<String, Callable<String>>taskMap = new HashMap<String, Callable<String>>();
for(int i=1; i<=5; i++) {
//随机产生坏任务
int r = (int)(Math.random()*5 + 1);
boolean badTask = (i==r);
taskMap.put("Task: " + i ,new TestMessageTask("Task: " + i, badTask));
}
MessageThreadPoolExecutor executor = new MessageThreadPoolExecutor(3, 5, 3000L,
TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(2));
try {
Map<String, String> resultMap = executor.performTasks(taskMap);
for(String key : resultMap.keySet()) {
System.out.println(resultMap.get(key));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试结果:
java.lang.RuntimeException: Task: 5 is bad task!!
java.lang.RuntimeException: Task: 2 is bad task!!
Task: 3 completed.
Task: 4 completed.
Task: 1 completed.