zoukankan      html  css  js  c++  java
  • 对线程池简单理解

    线程池的好处:

    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

  • 相关阅读:
    2.17-2.23第一周总结
    10号总结
    9日总结
    8号总结
    7号寒假总结
    6号
    读后感《程序员的修炼之道:从小工到专家》1
    java第二次动手动脑
    回文判断
    二进制的原码,反码以及补码介绍
  • 原文地址:https://www.cnblogs.com/ScarecrowAnBird/p/6801975.html
Copyright © 2011-2022 走看看