对线程池简单理解

爷,独闯天下 提交于 2020-03-20 03:04:03

线程池的好处:

1,因为线程是比较昂贵的资源,避免大量重复创建销毁线程,使用者不用关心创建销毁线程。

2,用户提交的任务能够及时的得到处理,提高响应速度。

3,能够更好的监控和管理线程。

ThreadPoolExecutor参数

 

    • int corePoolSize 
      • 线程池基本大小
    • int maximumPoolSize 
      • 线程池最大大小
    • long keepAliveTime 
      • 保持活动时间
    • TimeUnit unit 
      • 保持活动时间单位
    • BlockingQueue workQueue 
      • 工作队列
    • ThreadFactory threadFactory 
      • 线程工厂
    • RejectedExecutionHandler handler 
      • 驳回回调

这些参数这样描述起来很空洞,下面结合执行任务的流程来看一下。

ThreadPoolExecutor执行任务流程

当线程池大小 >= corePoolSize 且 队列未满时,这时线程池使用者与线程池之间构成了一个生产者-消费者模型。线程池使用者生产任务,线程池消费任务,任务存储在BlockingQueue中,注意这里入队使用的是offer,当队列满的时候,直接返回false,而不会等待。

keepAliveTime

当线程处于空闲状态时,线程池需要对它们进行回收,避免浪费资源。但空闲多长时间回收呢,keepAliveTime就是用来设置这个时间的。默认情况下,最终会保留corePoolSize个线程避免回收,即使它们是空闲的,以备不时之需。但我们也可以改变这种行为,通过设置allowCoreThreadTimeOut(true)

workQueue

线程池所使用的缓冲队列,该缓冲队列的长度决定了能够缓冲的最大数量,缓冲队列有三种通用策略:

1) 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性;

2) 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性;

3) 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

TimeUnit.DAYS;               //天  
TimeUnit.HOURS;             //小时  
TimeUnit.MINUTES;           //分钟  
TimeUnit.SECONDS;           //秒  
TimeUnit.MILLISECONDS;      //毫秒  
TimeUnit.MICROSECONDS;      //微妙  
TimeUnit.NANOSECONDS;       //纳秒 

三种常用的 ThreadPoolExecutor

Executors 是提供了一组工厂方法用于创建常用的 ExecutorService ,分别是 FixedThreadPool,CachedThreadPool 以及 SingleThreadExecutor。这三种ThreadPoolExecutor都是调用 ThreadPoolExecutor 构造函数进行创建,区别在于参数不同。

FixedThreadPool - 线程池大小固定,任务队列无界

下面是 Executors 类 newFixedThreadPool 方法的源码:

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

可以看到 corePoolSize 和 maximumPoolSize 设置成了相同的值,此时不存在线程数量大于核心线程数量的情况,所以KeepAlive时间设置不会生效。任务队列使用的是不限制大小的 LinkedBlockingQueue ,由于是无界队列所以容纳的任务数量没有上限。

因此,FixedThreadPool的行为如下:

  1. 从线程池中获取可用线程执行任务,如果没有可用线程则使用ThreadFactory创建新的线程,直到线程数达到nThreads

  2. 线程池线程数达到nThreads以后,新的任务将被放入队列

FixedThreadPool的优点是能够保证所有的任务都被执行,永远不会拒绝新的任务;同时缺点是队列数量没有限制,在任务执行时间无限延长的这种极端情况下会造成内存问题。

SingleThreadExecutor - 线程池大小固定为1,任务队列无界

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

这个工厂方法中使用无界LinkedBlockingQueue,并的将线程数设置成1,除此以外还使用FinalizableDelegatedExecutorService类进行了包装。这个包装类的主要目的是为了屏蔽ThreadPoolExecutor中动态修改线程数量的功能,仅保留ExecutorService中提供的方法。虽然是单线程处理,一旦线程因为处理异常等原因终止的时候,ThreadPoolExecutor会自动创建一个新的线程继续进行工作。

 

SingleThreadExecutor 适用于在逻辑上需要单线程处理任务的场景,同时无界的LinkedBlockingQueue保证新任务都能够放入队列,不会被拒绝;缺点和FixedThreadPool相同,当处理任务无限等待的时候会造成内存问题。

CachedThreadPool - 线程池无限大(MAX INT),等待队列长度为1

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

SynchronousQueue是一个只有1个元素的队列,入队的任务需要一直等待直到队列中的元素被移出。核心线程数是0,意味着所有任务会先入队列;最大线程数是Integer.MAX_VALUE,可以认为线程数量是没有限制的。KeepAlive时间被设置成60秒,意味着在没有任务的时候线程等待60秒以后退出。CachedThreadPool对任务的处理策略是提交的任务会立即分配一个线程进行执行,线程池中线程数量会随着任务数的变化自动扩张和缩减,在任务执行时间无限延长的极端情况下会创建过多的线程。

 
  1. 为什么newFixedThreadPool中要将corePoolSize和maximumPoolSize设置成一样? 答:因为newFixedThreadPool中用的是LinkedBlockingQueue(是无界队列),只要当前线程大于等于corePoolSize来的任务就直接加入到无界队列中,所以线程数不会超过corePoolSize,这样maximumPoolSize没有用。例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. 为什么newFixedThreadPool中队列使用LinkedBlockingQueue?答:设置的corePoolSize 和 maximumPoolSize相同,则创建的线程池是大小固定的,要保证线程池大小固定则需要LinkedBlockingQueue(无界队列)来保证来的任务能够放到任务队列中,不至于触发拒绝策略。
  3. 为什么newFixedThreadPool中keepAliveTime会设置成0?因为corePoolSize和maximumPoolSize一样大,KeepAliveTime设置的时间会失效,所以设置为0。
  4. 为什么newCachedThreadPool中要将corePoolSize设置成0?答:因为队列使用SynchronousQueue,队列中只能存放一个任务,保证所有任务会先入队列,用于那些互相依赖的线程,比如线程A必须在线程B之前先执行。
  5. 为什么newCachedThreadPool中队列使用SynchronousQueue?答:线程数会随着任务数量变化自动扩张和缩减,可以灵活回收空闲线程,用SynchronousQueue队列整好保证了CachedTheadPool的特点。
  6. 为什么newSingleThreadExecutor中使用DelegatedExecutorService去包装ThreadPoolExecutor?答:SingleThreadExecutor是单线程化线程池,用DelegatedExecutorService包装为了屏蔽ThreadPoolExecutor动态修改线程数量的功能,仅保留Executor中的方法。

 参考网址:http://blog.csdn.net/ghsau/article/details/53538303

https://segmentfault.com/a/1190000008394155

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!