一个简单的ThreadPool
原文来自
http://www.informit.com/articles/printerfriendly.asp?p=30483&r1=1&rl=1 项目是多线程的,所以引入了线程池这个东西。池子是个老美写的。在项目中表现的还不错。所以把它摘出来,介绍给以后或许需要用到它的同行们。
关于为什么要采用ThreadPool,原文已经提到了:创建一个线程是需要开销的;如果线程数量过大的话,cpu就会浪费很大的精力做线程切换。
ThreadPool的实现过程就是对WorkerThread的同步和通信的管理过程。
我们来看代码。
首先,在ThreadPool构造的时候,创建10个WorkerThread(size=10)并让他们运行。每个WorkerThread线程都有个ThreadPool的引用,用于查询ThreadPool的状态和获得同步锁.WorkerThread运行以后,循环调用ThreadPool的方法进行查询,如果没有发现任务,ThreadPool告诉正在查询的线程进入休眠状态,WorkerThread释放对查询方法的锁定.这样在还没有任务的时候,所有的10个WorkerThread都会进入休眠状态,进入等待ThreadPool对象的等待锁定池,只有ThreadPool对象发出notify方法(或notifyAll)后WorkerThread线程才进入对象锁定池准备获得对象锁进入运行状态。
代码片断:
while ( !assignments.iterator().hasNext() )
wait();
如果你有jprofile或者其他的观察线程的工具,你可以看到有10个线程都在休眠状态.
接着,我们向ThreadPool中加入任务,这些任务都实现了Runnable的run方法.(至于为什么把任务都做成Runnable,译者至今也有些疑问?预定俗成?TimerTask也是实现自Runnable,弄得初学者经常把真正运行的线程搞混).ThreadPool每assign一个任务,就会发出一条消息,通知它的等待锁定池中的线程.各个线程以抢占的方式获得对象锁,然后很顺利的获得一条任务.并把此任务从ThreadPool里面删除.没有抢到的继续等待.
Runnable r = (Runnable)assignments.iterator().next();
assignments.remove(r);
WorkerThread从ThreadPool那里获得了任务,继续向下执行。
target = owner.getAssignment();
if (target!=null) {
target.run();
owner.done.workerEnd();
}
记住,这里调用的是target.run();而不是调用的线程的start()方法。也就是说在这里表现出的WorkerThread和task之间的关系仅仅是简单的方法调用的关系,并没有额外产生新线程。(这就是我上面纳闷为什么大家都实现Runnable来做task的原因)
大家可能注意到,WorkerThread并没有对异常作处理。而我们知道发生在线程上的异常会导致线程死亡。解决的办法有2中,一种是通过threadpool的管理来重新激起一个线程,一种是把异常在线程之内消灭。在项目中,我采用的是第二中,因此这个片断改称这样:
if (target!=null) {
try{
target.run();
}
catch(Throwable t){
.......
}
owner.done.workerEnd();
}
在WorkerThread完成一个task以后,继续循环作同样的流程.
在这个ThreadPool的实现里面,Jeff Heaton用了一个Done类来观察WorkerThread的执行情况.和ThreadPoool的等待锁定池不同,Done的等待锁定池里面放的是初始化ThreadPool的线程(可能是你的主线程),我们叫他母线程.
在给出的测试例子中.母线程在调用complete()方法后进入休眠(在监视中等待),一开始是waitBegin()让他休眠,在assign加入task以后,waitDone()方法让他休眠.在WorkerThread完成一个task以后,通知waitDone()起来重新检查activeThreads的数值.若不为0,继续睡觉.若为0,那么母线程走完,死亡(这个时候该做的task已经做完了).母线程走完,ThreadPool还存在吗?答案是存在,因为WorkerThread还没有消亡,他们在等待下一批任务,他们有ThreadPool的引用,保证ThreadPool依然存在.大家或许已经明白Done这个类的作用了.
细心的读者或许会发现,发生在Done实例上的notify()并不是像ThreadPool上的notify()那样每次都能完成一项工作.比如除了第一个被assign的task,其他的task在assign进去的时候,发出的notify()对于waitDone()来说是句"狼来了".
最后在ThreadPool需要被清理得时候,使每一个WorkerThread中断(这个时候或许所有的WorkerThread都在休眠)并销毁.记住这里也是一个异步的过程.等到每一个WorkerThread都已经销毁,finalize()的方法体走完.ThreadPool被销毁.
for (int i=0;i<threads.length;i++) {
threads[i].interrupt();
done.workerBegin();
threads[i].destroy();
}
done.waitDone();
为什么有句done.workerBegin();?不明白.
参考文章:
http://www.zdnet.com.cn/developer/common/printfriend/printfriendly.htm?AT=39276905-3800066897t-20000560c