在任务与执行策略之间的隐形耦合

线程饥饿死锁

在线程池中,如果任务依赖于其他任务,那么可能会发生死锁。只要线程池中的任务需要无限期地等待一些必须由池中其他任务才能提供的资源或条件,例如某个任务等待另一个任务的返回值或执行结果,那么除非线程池足够大,否则将发生线程饥饿死锁。

运行时间较长的任务

如果任务阻塞的时间过长,那么即使不出现死锁,线程池的响应性也会变得糟糕。有一项技术可以缓解执行时间较长任务造成的影响,即限定任务等待资源的时间,而不是无限制的等待,例如:Thread.join,BlockingQueue.put,CountDownLatch.await……。如果等待超时,啊么可以把任务标识为失败,然后中止任务或者将任务重新放回队列以便随后执行。这样无论任务的最终结果是否成功,这种办法都能保证任务总能继续执行下去,并将线程释放出来以执行一些能更快完成的任务。

配置ThreadPoolExecutor

ThreadPoolExecutor为一些Executor提供了基本的实现,这些Executor是由Executors中的newCachedThreadPool,newFixedThreadPool和newScheduledThreadExecutor等工厂方法返回的。
可通过ThreadPoolExecutor的构造函数实例化一个对象来定制自己的需求。

  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue,
  6. ThreadFactory threadFactory,
  7. RejectedExecutionHandler handler) {...}

线程池的创建和销毁

线程池的基本大小(corePoolSize)、最大大小(maximumPoolSize)以及存活时间等因素共同负责线程的创建和销毁,基本大小也就是线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程,线程池的最大大小表示可同时活动的线程数量的上限,如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过了基本大小时,这个线程将被终止。

任务队列

如果无限制的创建线程,那么将导致系统不稳定,ThreadPoolExecutor允许提供一个BlockingQueue来保存的古代执行的任务。基本的任务排队方法有三种:无界队列、有界队列、同步移交。
无界队列:newFixedThreadPool,newSingleThreadExecutor在默认情况下将使用一个无界的LinkedBlockingQueue。如果所有的工作者线程都处于忙碌状态,那么任务将在队列中等候。如果任务持续快速的到达,并且超过了线程池处理他们的速度,那么队列将无限制的增加。
有界队列:有界队列如ArrayBlockingQueue,有界的LinkedBlockingQueue、PriorityBlockingQueue,有界队列有助于避免资源耗尽的情况发生,但它又带来新的问题,队列满了,新的任务怎么办?在使用有界的工作队列时,队列的大小与线程池的大小必须一起调节。如果线程池较小而队列较大,那么有助于减少内存使用量,降低cpu的使用率,同时还可以减少上下文切换,但付出的代价使可能会限制吞吐量。
同步移交:对于非常大的或者无界的线程池,可使用SynchronousQueue来避免任务排队,以及直接将任务从生产者移交给工作者线程。它不是一个真正的队列,而是一种在线程之间进行移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接受这个元素。如果没有线程正在等待,并且线程池的当前大小小于最大值,那么ThreadPoolExecutor将创建一个新的线程,否则根据饱和策略,这个任务将被拒绝。使用直接移交将更高效,因为任务会直接移交给执行它的线程,而不是被首先放在队列中,然后由工作者线程从队列中提取该任务。

饱和策略

当有界队列填满后,饱和策略开始发挥作用。ThreadPoolExecutor的饱和策略可以通过调用setRejectedExecutionHandler来修改。JDK提供了几种不同的RejectedExecutionHandler实现:AbortPolicy、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy。

小结

对于并发执行的任务,Executor框架是一种强大且灵活的框架。它提供了大量可调节的选项。例如创建线程和关闭线程的策略,处理队列任务的策略,处理过多任务的策略,并且提供了几个钩子方法来扩展它的行为。然而,与大多数功能强大的框架一样,其中有些设置参数并不能很好的工作,某些类型的任务需要特定的执行策略,而一些参数组合则可能产生奇怪的结果。