ThreadPoolExecutor底层诠释

ThreadPoolExecutor: 一个ExecutorService使用可能的几个池线程之一执行每个提交的任务,通常使用Executors工厂方法配置。
线程池解决两个不同的问题:由于减少了每个任务的调用开销,它们通常在执行大量异步任务时提供改进的性能,并且它们提供了一种限制和管理资源的方法,包括在执行集合时消耗的线程任务。 每个ThreadPoolExecutor还维护一些基本的统计信息,例如已完成的任务数。
为了在广泛的上下文中有用,此类提供了许多可调整的参数和可扩展性挂钩。 但是,强烈建议程序员使用更方便的Executors工厂方法Executors.newCachedThreadPool (无界线程池,具有自动线程回收)、 Executors.newFixedThreadPool (固定大小线程池)和Executors.newSingleThreadExecutor (单个后台线程),它们为最常见的使用场景。 否则,在手动配置和调整此类时使用以下指南:
核心和最大池大小
ThreadPoolExecutor将根据 corePoolSize(请参阅getCorePoolSize )和getCorePoolSize (请参阅getMaximumPoolSize )设置的边界自动调整池大小(请参阅getPoolSize )。 当在方法execute(Runnable)提交新任务,并且正在运行的线程少于 corePoolSize 时,即使其他工作线程空闲,也会创建一个新线程来处理请求。 如果有超过 corePoolSize 但小于 maximumPoolSize 的线程正在运行,则只有在队列已满时才会创建新线程。 通过将 corePoolSize 和 maximumPoolSize 设置为相同,您可以创建一个固定大小的线程池。 通过将 maximumPoolSize 设置为一个基本上无界的值,例如Integer.MAX_VALUE ,您可以允许池容纳任意数量的并发任务。 最典型的是,核心和最大池大小仅在构造时设置,但它们也可以使用setCorePoolSize和setMaximumPoolSize动态更改。
按需构建
默认情况下,即使是核心线程也只有在新任务到达时才最初创建和启动,但这可以使用方法prestartCoreThread或prestartAllCoreThreads动态覆盖。 如果您使用非空队列构造池,您可能想要预启动线程。
创建新线程
使用ThreadFactory创建新线程。 如果没有另外指定,则使用Executors.defaultThreadFactory ,它创建的线程都在同一个ThreadGroup并且具有相同的NORM_PRIORITY优先级和非守护进程状态。 通过提供不同的 ThreadFactory,您可以更改线程的名称、线程组、优先级、守护进程状态等。如果ThreadFactory在通过从newThread返回 null 的询问时未能创建线程,则执行程序将继续,但可能无法执行任何任务。 线程应该拥有“modifyThread” RuntimePermission 。 如果工作线程或其他使用池的线程不具备此权限,则服务可能会降级:配置更改可能无法及时生效,关闭池可能会一直处于可以终止但未完成的状态。
保活时间
如果池中当前有超过 corePoolSize 的线程,则多余的线程如果空闲时间超过 keepAliveTime(请参阅getKeepAliveTime(TimeUnit) )将被终止。 这提供了一种在未积极使用池时减少资源消耗的方法。 如果池稍后变得更加活跃,则将构建新线程。 也可以使用setKeepAliveTime(long, TimeUnit)方法动态更改此参数。 使用Long.MAX_VALUE TimeUnit.NANOSECONDS值Long.MAX_VALUE有效地禁止空闲线程在关闭之前终止。 默认情况下,仅当有超过 corePoolSize 的线程时,保持活动策略才适用。 但是方法allowCoreThreadTimeOut(boolean)也可用于将此超时策略应用于核心线程,只要 keepAliveTime 值非零即可。
排队
任何BlockingQueue都可用于传输和保存提交的任务。 此队列的使用与池大小交互:
如果运行的线程数少于 corePoolSize,则 Executor 总是喜欢添加新线程而不是排队。
如果 corePoolSize 或更多线程正在运行,Executor 总是喜欢将请求排队而不是添加新线程。
如果请求无法排队,则会创建一个新线程,除非这会超过 maximumPoolSize,在这种情况下,任务将被拒绝。
排队的一般策略有以下三种:
直接交接。 工作队列的一个很好的默认选择是SynchronousQueue ,它将任务移交给线程而不用其他方式保留它们。 在这里,如果没有线程可立即运行,则将任务排队的尝试将失败,因此将构建一个新线程。 在处理可能具有内部依赖性的请求集时,此策略可避免锁定。 直接切换通常需要无限的maximumPoolSizes 以避免拒绝新提交的任务。 这反过来又承认了当命令平均持续到达速度快于它们可以处理的速度时无限线程增长的可能性。
无界队列。 使用无界队列(例如,没有预定义容量的LinkedBlockingQueue )将导致新任务在所有 corePoolSize 线程都忙时在队列中等待。 因此,不会创建超过 corePoolSize 的线程。 (因此maximumPoolSize的值没有任何影响。)当每个任务完全独立于其他任务时,这可能是合适的,因此任务不会影响彼此的执行; 例如,在网页服务器中。 虽然这种排队方式在平滑请求的瞬时爆发方面很有用,但它承认当命令的平均到达速度超过它们的处理速度时,工作队列可能会无限增长。
有界队列。 有界队列(例如, ArrayBlockingQueue )在与有限的 maximumPoolSizes 一起使用时有助于防止资源耗尽,但可能更难以调整和控制。 队列大小和最大池大小可以相互权衡:使用大队列和小池可以最大限度地减少 CPU 使用率、操作系统资源和上下文切换开销,但会导致人为地降低吞吐量。 如果任务频繁阻塞(例如,如果它们受 I/O 限制),则系统可能能够为比您允许的更多线程安排时间。 使用小队列通常需要更大的池大小,这会使 CPU 更忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。
被拒绝的任务
当 Executor 已经关闭,并且当 Executor 对最大线程和工作队列容量使用有限边界并且饱和时,在方法execute(Runnable)提交的新任务将被拒绝。 在任一情况下, execute方法调用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)其的方法RejectedExecutionHandler 。 提供了四个预定义的处理程序策略:
在默认的ThreadPoolExecutor.AbortPolicy ,处理程序在拒绝时抛出运行时RejectedExecutionException 。
在ThreadPoolExecutor.CallerRunsPolicy ,调用execute自身的线程运行任务。 这提供了一个简单的反馈控制机制,可以减慢提交新任务的速度。
在ThreadPoolExecutor.DiscardPolicy ,无法执行的任务被简单地丢弃。
在ThreadPoolExecutor.DiscardOldestPolicy ,如果执行器没有关闭,工作队列头部的任务会被丢弃,然后重试执行(可能会再次失败,导致重复执行)。
可以定义和使用其他类型的RejectedExecutionHandler类。 这样做需要小心,特别是当策略设计为仅在特定容量或排队策略下工作时。
钩子方法
此类提供protected可beforeExecute(Thread, Runnable)和afterExecute(Runnable, Throwable)方法,这些方法在每个任务执行之前和之后调用。 这些可用于操作执行环境; 例如,重新初始化 ThreadLocals、收集统计信息或添加日志条目。 此外,可以覆盖已terminated方法以执行在 Executor 完全终止后需要完成的任何特殊处理。
如果钩子或回调方法抛出异常,内部工作线程可能会失败并突然终止。
队列维护
方法getQueue()允许访问工作队列以进行监视和调试。 强烈建议不要将此方法用于任何其他目的。 提供的两种方法remove(Runnable)和purge可用于在大量排队任务被取消时协助存储回收。
定稿
这在程序不再被引用,也没有剩余的线程将成为池shutdown自动。 如果您想确保即使用户忘记调用shutdown也能回收未引用的池,那么您必须通过设置适当的保持活动时间、使用零核心线程的下限和/或设置allowCoreThreadTimeOut(boolean)来安排未使用的线程最终死亡allowCoreThreadTimeOut(boolean) 。
扩展示例。 此类的大多数扩展都会覆盖一个或多个受保护的挂钩方法。 例如,这是一个添加简单暂停/恢复功能的子类:

    class PausableThreadPoolExecutor extends ThreadPoolExecutor {
        private boolean isPaused;
        private ReentrantLock pauseLock = new ReentrantLock();
        private Condition unpaused = pauseLock.newCondition();

        public PausableThreadPoolExecutor(...) { super(...); }

        protected void beforeExecute(Thread t, Runnable r) {
            super.beforeExecute(t, r);
            pauseLock.lock();
            try {
                while (isPaused) unpaused.await();
            } catch (InterruptedException ie) {
                t.interrupt();
            } finally {
                pauseLock.unlock();
            }
        }

        public void pause() {
            pauseLock.lock();
            try {
                isPaused = true;
            } finally {
                pauseLock.unlock();
            }
        }

        public void resume() {
            pauseLock.lock();
            try {
                isPaused = false;
                unpaused.signalAll();
            } finally {
                pauseLock.unlock();
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值