线程池那些事儿

1 为什么要用线程池
线程也是一种对象,万事万物皆对象,线程池也就是对象池,通过将大对象池化,来达到对象复用,从而节省对象的内存开销以及对象的创建开销,设计模式中的享元模式就是将对象池化的一种模式,比较常听说的还有数据库连接池。对象池经常池化大对象和创建时间较长的对象,由于Java线程依赖于操作系统内核线程,所以线程创建需要切换到内核态,创建和销毁的消耗比较大。

另外线程属于稀缺资源,不能无限制的创建,通过线程池可以进行统一的创建和监控。 遇到了性能瓶颈,吞吐量上不去的时候,并不是一味的加大线程数就能提高性能,线程达到一定的程度,上下文切换会消耗很大的CPU,并且可能是在做无用功,所以线程数量要有一个度,凡事都应该有个度,常言道"过犹不及"。要找到最佳的线程数,需要根据应用的类型,比如CPU密集型还是IO密集型,CPU的个数,IO等待和执行的比率,以及程序串行化的比率等因素综合考虑,并通过性能测试来具体验证才能获得最优配置,毕竟实践是检验真理的唯一标准嘛,废话少说,抓紧时间看看线程池吧。

2 类图

[img]http://dl2.iteye.com/upload/attachment/0104/1636/ecdd0393-5392-31ba-8c64-144c0b0187ff.png[/img]

这里最关键的是ThreadPoolExecutor类,隐藏了内部类和方法,不然太多太乱了,这样比较简洁,简洁是王道,简洁而非简陋。Executors提供了几个推荐使用的工厂方法。BlockingQueue用于存放暂时不能执行的线程队列。下面分别来看看这个关键类。

3 ThreadPoolExecutor的构造方法


ThreadPoolExecutor的构造方法

/**
* @param corePoolSize the number of threads to keep in the
* pool, even if they are idle.
* @param maximumPoolSize the maximum number of threads to allow in the
* pool.
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the keepAliveTime
* argument.
* @param workQueue the queue to use for holding tasks before they
* are executed. This queue will hold only the <tt>Runnable</tt>
* tasks submitted by the <tt>execute</tt> method.
* @param threadFactory the factory to use when the executor
* creates a new thread.
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached.
* */
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)


简单翻译一下,英语好的直接看英文,那才是最官方,最权威的。
corePoolSize:即使他们(线程)是空闲的,仍然保存在池中的线程数。
maximumPoolSize:线程池允许的最大线程数。
keepAliveTime:如果线程数大于corePoolSize,如果线程超过这个时间,将会被干掉。
unit:跟keepAliveTime对应的时间单位。
workQueue:该队列用户存放未被执行的任务。仅仅存放通过execute方法提交的任务。
threadFactory:用于创建新线程的工厂。
handler:如果线程不能被执行的拒绝策略。

其他的都容易理解,corePoolSize 比较容易引起误解,下面解释一下corePoolSize和线程池的处理流程。
corePoolSize:同其他的线程池不同,线程池并不是一上来就创建corePoolSize的线程数等待执行,一开始是空的线程池,提交一个任务到线程池中才会创建一个线程来执行。在线程数未达到corePoolSize之前,即使池中有空闲的线程,也仍然会创建一个新的线程来执行。
corePoolSize的线程不会被回收。

线程池的处理流程,这个图画的好,笔者直接借过来用一下:

[img]http://dl2.iteye.com/upload/attachment/0104/1638/8a552dd4-c98f-3436-bc5f-80a37c305faa.png[/img]

1)是否小于corePoolSize?创建新线程为其服务:请看2。
2)workQueue是否已满?请看3:将新提交的任务存储到workQueue中
3)是否小于maximumPoolSize?创建新的线程为其服务:按handler策略处理。


如果workQueue是无界的,那么maximumPoolSize将无意义。

真正结合自己的业务场景,正确理解和使用好这些参数,才是最难的。


4 Executors的几个工厂方法
作者建议使用Executors的方法来创建ThreadPoolExecutor对象。
1)public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
使用它则始终只有corePoolSize个线程在争抢CPU资源执行,其他的都得老老实实的在队列中候着。
其中corePoolSize和maximumPoolSize相等,LinkedBlockingQueue是一个无界的workQueue。
由于是个无界的queue,所以如果线程执行的时间很长,并且不停的向其提交新的任务的话,会一直放入queue,会导致系统垮掉。
2)public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
上面的一个特例,nThreads = 1

3)public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
由于maximumPoolSize = Integer.MAX_VALUE,所以如果新提交任务时,所有的线程都在工作的话,会一直创建新的线程,直至系统垮掉。线程执行完毕,空闲60s的话,会被回收掉,不能占着茅坑不拉屎。如果空闲期间来了新的任务,则空闲线程正好可以被复用。


通常为了保障系统的高可用,要限制资源的使用,其中就包括线程资源,所以建议使用有界队列,这样虽然某些请求会失败,但是最起码可以保障整个系统的可用性。

5 拒绝策略
ThreadPoolExecutor中有几个实现RejectedExecutionHandler的内部类:

AbortPolicy: 这个比较暴力,直接抛异常,拒绝执行。
DiscardPolicy:这个是个空实现,直接丢弃。
DiscardOldestPolicy:丢弃队列中的,执行新来的。喜新厌旧啊这个。
CallerRunsPolicy: 使用调用者线程执行任务。

6 BlockingQueue
ArrayBlockingQueue:基于数组结构的阻塞队列。
LinkedBlockingQueue:基于链表结构的阻塞队列。 Executors.newFiexedThreadPool()使用的就是这个队列。
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程的移除操作。Executors.newCachedThreadPool()就是使用这个队列。也就是说如果此时有空闲线程则转交给空闲线程处理,如果没有空闲线程则会增加新的线程来处理。

7 线程池的关闭
线程池的状态:
RUNNING: 接受新任务,执行队列中的任务。
SHUTDOWN: 不接受新任务,但是执行队列中的任务。RUNNING->SHUTDOWN,调用shutdown()方法。
STOP:不接受新任务,不执行队列中的任务,并终止正在执行的任务。RUNNING或者SHUTDOWN->STOP,调用shutdownNow()方法。
TERMIANTED:同STOP,但是所有线程已经terminated。

线程池一定要记得shutdown(),这就跟创建了数据库资源finally中记得关闭一样,如Connection ResultSet PreparedStatement Statement,还有文件,网络socket对象,创建后都要记得关闭。


8 部分源码分析
ThreadPoolExecutor:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//如果小于corePoolSize,就通过addIfUnderCorePoolSize
//创建新的线程来处理任务
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
//如果大于corePoolSize,则优先放入队列中。
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
//如果队列中满了则addIfUnderMaximumPoolSize看是否达到了maximumPoolSize,如果未达到则创建新的线程,如果达到了则拒绝。
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
代码很短,但是掌握着核心科技,第3部分讲述的线程池处理流程,就是这段代码。


ThreadPoolExecutor:
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < corePoolSize && runState == RUNNING)
//创建新的线程
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}


ThreadPoolExecutor:
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
//创建新的线程
Thread t = threadFactory.newThread(w);
if (t != null) {
//跟Worker工作线程关联
w.thread = t;
workers.add(w);
int nt = ++poolSize;
if (nt > largestPoolSize)
largestPoolSize = nt;
}
return t;
}


ThreadPoolExecutor内部类 Worker:
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
//通过getTask()循环到工作队列中获取任务
while (task != null || (task = getTask()) != null) {
//执行任务
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}


9 代码示例
public class TestThreadPool {

private static int produceTaskSleepTime = 2;

private static int produceTaskMaxNumber = 10;

public static void main(String[] args) {

// 构造一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardPolicy());


// ExecutorService threadPool = Executors.newCachedThreadPool();

for (int i = 1; i <= produceTaskMaxNumber; i++) {
try {
String task = "task@ " + i;
System.out.println("创建任务并提交到线程池中:" + task);
threadPool.execute(new ThreadPoolTask(task));

Thread.sleep(produceTaskSleepTime);
} catch (Exception e) {
e.printStackTrace();
}
}
threadPool.shutdown();
}
}


public class ThreadPoolTask implements Runnable, Serializable {

private Object attachData;

ThreadPoolTask(Object tasks) {
this.attachData = tasks;
}

public void run() {

System.out.println("开始执行任务:" + attachData);
try{
Thread.sleep(10000);
}catch (InterruptedException e){
e.printStackTrace();
}
attachData = null;
}

public Object getTask() {
return this.attachData;
}
}


执行结果:
创建任务并提交到线程池中:task@ 1
开始执行任务:task@ 1
创建任务并提交到线程池中:task@ 2
开始执行任务:task@ 2
创建任务并提交到线程池中:task@ 3
创建任务并提交到线程池中:task@ 4
创建任务并提交到线程池中:task@ 5
创建任务并提交到线程池中:task@ 6
开始执行任务:task@ 6
创建任务并提交到线程池中:task@ 7
开始执行任务:task@ 7
创建任务并提交到线程池中:task@ 8
创建任务并提交到线程池中:task@ 9
创建任务并提交到线程池中:task@ 10
开始执行任务:task@ 3
开始执行任务:task@ 4
开始执行任务:task@ 5
可见:task@ 1和task@ 2提交时,由于小于corePoolSize:2,所以创建了新的线程来执行,
task@ 3 task@ 4 task@ 5,由于大于了corePoolSize,这时要将他们放入到工作队列中等待被执行。
task@ 6 task@ 7因为工作队列的大小是3,所以工作队列放入 task@ 3 task@ 4 task@ 5就已经满了,此时会创建新的线程来执行。
task@ 8 task@ 9 task@ 10,这时候执行的线程有task@ 1和task@ 2 task@ 6 task@ 7,已经超过了maximumPoolSize,所以采用new ThreadPoolExecutor.DiscardPolicy() 拒绝策略,直接抛弃。
之后由之前执行完成的线程从队列中获取task@ 3 task@ 4 task@ 5执行。

10 应用场景
线程池适合有大量的线程,并且每个线程执行的时间很短的应用场景,可以节约线程创建和销毁的时间开销,如果执行的时间很长,将会有很多线程长期得不到服务,不如new Thread。


各位看官,到这里就差不多结束了,天下无不散之筵席,看完不谢,请叫我雷锋,红领巾也行。。


11 参考资料
java并发编程的艺术-迷你书 *
http://suhuanzheng7784877.iteye.com/blog/1167218 *
http://www.enjoylinux.cn/news_view.asp?id=745
http://blog.youkuaiyun.com/cutesource/article/details/6061229
http://blog.youkuaiyun.com/wangwenhui11/article/details/6760474 *
http://dongxuan.iteye.com/blog/901689 *
http://iamzhongyong.iteye.com/blog/1458349
http://wenku.baidu.com/link?url=MCYo2goOeZX8OcQxQtjsYS2EHvDUHzgwyk9nuN-IBUnNUCqBGBctUJ__fp8oDBSmEo5C1sGUW7_j1I7eDxaI7KDwp0gywYkM4Gpr3YWc_HW
Java性能优化-葛一鸣
http://wenku.baidu.com/link?url=AIbcjXDXGGj3opSys9tRNlPDoebFCm4jGLIPiIu3gU3GsfjthCZhPk-h4u-
TlsoTKiYFS_bUeiS_0fBibIOhyF7D5BqVjxziGmcZYOx68M_
http://www.blogjava.net/xylz/archive/2010/12/19/341098.html *
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值