拾贝壳

走过的路
随笔 - 39, 文章 - 1, 评论 - 14, 引用 - 0
数据加载中……

一个简单的ThreadPool分析

一个简单的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

posted on 2006-07-16 20:07 binge 阅读(7181) 评论(1)  编辑  收藏 所属分类: J2SE

评论

# re: 一个简单的ThreadPool分析  回复  更多评论   

纠正一个明显的错误:

----------
关于为什么要采用ThreadPool,原文已经提到了:创建一个线程是需要开销的,如果线程数量过大的话,cpu就会浪费很大的精力做线程切换。
----------

线程池的目的不是reuse,而是overload shield.这是初涉多线程编程的人经常会犯的概念性错误,也是对于线程池功能最常见的误解

况且,你指出的原文已经说的很明白了,你似乎没有理解原文的意思
原文:
Why a Thread Pool?
When programming the crawler in the previous section, a problem that would soon present itself is the number of threads to use. A crawler may have to visit tens of thousands of pages, and you certainly do not want to create tens of thousands of threads because each thread imposes a certain amount of overhead. If the number of threads grows too large, the computer will be spending all of its time switching between threads, rather than just executing them.

To solve this problem, you must create a thread pool. The thread pool is given some fixed number of threads to use. The thread pool will assign its tasks to each of these threads. As the threads finish with old tasks, new ones are assigned. This causes the program to use a fixed number of threads, not to be continually creating new threads.

-----------------

再说,这篇文章太老了,说java没有提供内建的线程池实现,java5.0已经提供了内建的线程池
2006-07-18 02:59 | fisher

只有注册用户登录后才能发表评论。


网站导航: