1、在任务与执行策略之间的隐性耦合
虽然Executor框架为制定和修改执行策略都提供了相当大的灵活性,但并非所有的任务都能适用所有的执行策略。有些类型的任务需要明确地指定执行策略,包括:1:依赖性任务;2:适用线程封闭机制的任务;3.对响应时间敏感的任务;4:适用ThreadLocal的任务;
(1)每次提交了一个有依赖性的Executor任务时,要清楚地知道可能会出现线程”饥饿:死锁,因此需要在代码或配置Executor的配置文件中记录线程池的大小限制与配置限制。
(2)运行时间较长的任务
限定任务等待资源的时间,可以缓解执行时间较长任务造成的影响。
在平台类库的大多数可阻塞方法中,都同时定义了显示版本和无限时版本。
例如:Thread.join、BlockingQueue.put、CountDownLatch.await以及Selector.select等
2、设置线程池的大小
线程池的理想大小取决于被提交任务的类型以及所部属系统的特性。在代码中,通常不会固定线程池的大小,而应该通过某种配置机制来提供,或者根据Runtime.availableProcessors来动态计算。
计算密集型的任务,在拥有N(cpu)个处理器的系统上,当线程池的大小为n+1时,通常能实现最优的利用率;
I/O密集型,由于线程并不会一直在执行,估算任务等待的时间与计算时间的比值;
U(cpu)=cpu的利用率,0=<U<=1; w/c = 计算机计算等待的时间/计算时间
线程池的最优大小:N(thread) = N(cpu)*U(cpu的效益)*(1+w/c)
注:影响线程池大小资源,还包括内存、文件句柄、套结子句柄和数据库连接等;
3、配置ThreadPoolExecutor
(1)线程的创建与销毁
线程池的基本大小、最大大小以及存活时间等因素共同负责线程的创建与销毁。
(2)管理队列任务
基本任务队列有3种:无界队列、有界队列和同步移交
无界队列:当任务超过了线程池处理它们的速度,那么队列将无线增加;
有界队列:就是队列的容量有限;
同步移交:避免任务排队,直接将任务从生产者移交给消费者;
(3)饱和策略
当有界队列被填满后,饱和策略开始发挥作用。ThreadPoolExecutor可以通过调用setrejectedExecutionHandler来修改;
RejectedExecutionHandler实现:AbortPolicy(默认),callerRunsPolicy,DiscardPolicy和DiscardOldestPolicy。
(4)线程工厂
每当线程池需要创建一个线程时,都是通过线程工厂方法来完成的。ThreadFactory接口中定义了一个方法:newThread();
(5)在调用构造函数后再定制ThreadPoolExecutor
在调用玩ThreadPoolExecutor的构造函数后,仍然可以通过设置函数(setter)来修改大多数传递给它的构造函数的参数;
4、扩展ThreadPoolExecutor
ThreadPoolExecutor是可扩展的,它提供了几个可以在子类化中改写的方法:beforeExecute、afterExecute和terminated,用于扩展ThreadPoolExecutor的行为。在这些方法中,可以添加日志、计时、监视或统计信息收集的功能。
在线程池完成关闭操作时调用terminated,可以用来释放Executor在其生命周期里分配的各种资源。此外,还可以执行发送通知、记录日志或者收集finalize统计信息等操作。
5、递归算法的并行化
当串行循环中的各个迭代操作彼此独立时,并行每个迭代操作执行的工作亮比管理一个新任务带来的开销更多,那么这个串行循环就适合并行化。