public static void main(String args[])throws TOException {
new EchoServer().service();
}
// 负责与单个客户的通信,与上例类似
class Handler implements Runnable {...}
}
复制代码
使用线程池的注意事项
虽然线程池能大大提高服务器的并发性能,但使用它也存在一定风险,容易引发下面的问题:
死锁
任何多线程应用程序都有死锁风险。造成死锁的最简单的情形是:线程 A 持有对象 X 的锁,并且在等待对象 Y 的锁,而线程 B 持有对象 Y 的锁,并且在等待对象 X 的锁,线程 A 与线程 B 都不释放自己持有的锁,并且等待对方的锁,这就导致两个线程永远等待下去,死锁就这样产生了
任何多线程程序都有死锁的风险,但线程池还会导致另外一种死锁:假定线程池中的所有工作线程都在执行各自任务时被阻塞,它们都在等待某个任务 A 的执行结果。而任务 A 依然在工作队列中,由于没有空闲线程,使得任务 A 一直不能被执行。这使得线程池中的所有工作线程都永远阻塞下去,死锁就这样产生了
了解任务的特点,分析任务是执行经常会阻塞的 IO 操作,还是执行一直不会阻塞的运算操作。前者时断时续地占用 CPU,而后者对 CPU 具有更高的利用率。根据任务的特点,对任务进行分类,然后把不同类型的任务分别加入不同线程池的工作队列中,这样可以根据任务的特点分别调整每个线程池
调整线程池的大小,线程池的最佳大小主要取决于系统的可用 CPU 的数目以及工作队列中任务的特点。假如在一个具有 N 个 CPU 的系统上只有一个工作队列并且其中全部是运算性质的任务,那么当线程池具有 N 或 N+1 个工作线程时,一般会获得最大的 CPU 利用率
如果工作队列中包含会执行 IO 操作并经常阻塞的任务,则要让线程池的大小超过可用 CPU 的数目,因为并不是所有工作线程都一直在工作。选择一个典型的任务,然后估计在执行这个任务的过程中,等待时间(WT)与实际占用 CPU 进行运算的时间(ST)之间的比:WT/ST。对于一个具有 N 个 CPU 的系统,需要设置大约 N(1+WT/ST) 个线程来保证 CPU 得到充分利用