1、任务与执行策略间的隐性耦合
一些任务具有这样的特征:需要或者排斥某种特定的执行策略。对其他任务具有依赖性的任务,就会要求线程池足够大,来保证它锁依赖任务不必排队或者不被拒绝;采用线程限制的任务需要顺序的执行。把这些需求都写入文档,这样将来的维护者就不会使用一个与原先相悖的执行策略,而破坏安全性或活跃度了。
只要池任务开始了无限期的阻塞,其目的是等待一些资源或条件,此时只有另一个池任务的活动才能使那些条件成立,比如等待返回值或者另一个任务的边界效应。这被成为线程饥饿死锁。
无论何时,你提交了一个非独立的Executor任务,要明确出现线程饥饿死锁的可能性,并在代码或者配置文件以及其他可以配置Executor的地方,任何有关池的大小和配置约束都要写入文档。
2、定制线程池大小(不要硬编码)
为保持处理器达到期望的使用率,最优的池的大小等于:
Nthreads = Ncpu*Ucpu*(1+W/C)
Ncpu = cpu的数量 Ucpu = 目标CPU的使用率,0<=Ucpu<=1 W/C等待时间与计算时间的比率
可以使用Runtime来获得CPU的数目: int N_CPUS = Runtime.getRuntime().availableProcessors();
其他可以约束资源池大小的资源包括:内存、文件句柄、套接字句柄和 数据库连接等。
3、配置ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
以上为最通用的构造函数。详细参考JDK1.6
newCachedThreadPool工厂提供了比定长的线程池更好的队列等候性能,它是Executor的一个很好的默认选择。处于资源管理的目的,当你需要限制当前任务的数量,一个定长的线程池就是很好的选择。就像一个接受网络客户端请求的服务器应用程序,如果不进行限制,就会很容易因为过载而遭受攻击。
只有当任务彼此独立时,才能使有限线程池或者有限工作队列的使用是合理的。
无论何时,线程池创建一个线程,都要通过一个线程工厂来完成。
4、扩展ThreadPoolExecutor
该类提供了beforeExecute、afterExecute和terminate这些来扩展行为。
5、并发递归算法
当每个迭代彼此独立,并且完成循环体中每个迭代的工作,意义都足够重大,足以弥补管理一个新任务的开销时,这个顺序循环是适合并行化的。
public void parallelRecursive(final Executor exec,List> nodes,final Collection results){ for(final Node n : nodes){ exec.execute(new Runnable){ public void run(){ results.add(n.compute()); } } parallelRecursive(exec,n.getChildren(),results); } } public Collection getParallelResults(List> nodes) throws InterruptedException{ ExecutorService exec = Executors.newCacheThreadPool(); Queue resultQueue = new ConcurrentLinkedQueue(); parallelRecursive(exec,nodes,resultQueue); exec.shutdown(); exec.awaitTermination(Long.MAX_VALUE,TimeUnit.SECONDS); return resultQueue; }