Posts 《Java并发编程实战》第8章线程池的使用
Post
Cancel

《Java并发编程实战》第8章线程池的使用

本章将介绍对线程池进行配置与调优的一些高级选项,并分析在使用任务执行框架时需要注意的各种危险,以及一些使用Executor的高级示例。

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

8.2 设置线程池的大小

8.3 配置ThreadPoolExecutor

8.3.1 线程的创建于销毁

8.3.2 管理队列任务

ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。基本的任务排队方法有3种:无界队列、有界队列和同步移交(Synchronous Handoff)。队列的选择与其他的配置参数有关,例如线程池的大小等。

newFixedThreadPoolnewSingleThreadExecutor在默认情况下使用一个无界队列LinkedBlockingQueue。如果所有工作线程都处于忙碌状态,那么任务将在队列中等候。如果任务持续快速地到达,并且超过了线程池处理它们的速度,那么队列将无限制地增加。

1
2
3
4
5
6
7
8
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
    this(Integer.MAX_VALUE); //默认容量为integer的最大值
}

一种更稳妥的资源管理策略是使用有界队列,例如ArrayBlockingQueue、有界的LinkedBlockingQueuePriorityBlockingQueue。有界队列有助于避免资源耗尽的情况发生,但它又带来了新的问题:当队列填满后,新的任务该怎么办?在使用有界的工作队列时,队列的大小与线程池的大小必须一起调节。如果线程池较小而队列较大,那么有助于减少内存使用量,降低CPU的使用率,同时还可以减少上下文切换,但付出的代价是可能会限制吞吐量。

对于非常大的或者无界的线程池,可以通过使用SynchronousQueue来避免任务排队,以及直接将任务从生产者移交给工作线程。SynchronousQueue不是一个真正的队列,而是一种在线程之间进行移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接受这个元素。如果没有线程正在等待,并且线程池的当前大小小于最大值,那么ThreadPoolExecutor将创建一个新的线程,否则根据饱和策略,这个任务将被拒绝。使用直接移交将更高效,因为任务会直接移交给执行它的线程,而不是被首先放在队列中,然后由工作线程从队列中提取该任务。只有当线程池是无界的或者可以拒绝任务时,SynchronousQueue才有实际价值。在newCachedThreadPool工厂方法中就使用了SynchronousQueue

当使用像LinkedBlockingQueueArrayBlockingQueue这样的FIFO队列时,任务的执行顺序与它们的到达顺序相同。如果想进一步控制任务执行顺序,还可以使用PriorityBlockingQueue,这个队列将根据优先级来安排任务。任务的优先级是通过自然顺序或Comparator来定义的。

8.3.3 饱和策略

8.3.4 线程工厂

每当线程池需要创建一个线程池时,都是通过线程工厂方法来完成的。

8.4 扩展ThreadPoolExecutor

参考

This post is licensed under CC BY 4.0 by the author.

《Java并发编程实战》第6章执行任务

《Java并发编程实战》第13章显式锁

Comments powered by Disqus.