Java并发编程——线程池

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;

      }

 



留言