Java线程池概述

内容提要 最近,在公司做了几次技术分享,将slides上的内容整理成博客。本文的主要内容包括如下几个部分: 背景 Java线程池类型 线程池参数与使用实例 ThreadPoolExecutor实现 总结 本文将主要讲解ThreadPoolExecutor的使用、参数解释以及内部实现,对于ScheduedThreadPoolExecutor与ForkJoinPool只会简单提及。 一、背景 我们为什么需要使用线程池,直接new Thread岂不是很方便?如果我们在编写Demo或者开发个小型的Java应用程序,那么这种方法的确没什么问题。但是如果我们在开发大型的企业级应用,那么直接创建线程是一种糟糕的实践,会造成如下问题: 直接new Thread使创建线程的代码分散在项目各个地方,不利于维护(指代码上的) 当你的Runnable执行完毕线程无法复用造成资源浪费 直接new Thread不遍于控制线程的数量(启动多少个线程不受限制,想new就new) 那么这些问题有什么缺点呢?对于第一条是显然的。对于第二条,你可能以为创建线程的代价和创建一个对象的代价差不多,但除了创建对象外,还可能会在OS层面创建线程(这取决于JVM实现)。线程是一种宝贵的资源(每个线程有自己的调用栈、PC、IR),频繁创建销毁开销很大。对于第三条,创建大量的线程显而易见会带来昂贵的调度开销,要知道线程调度需要做保留现场、恢复现场等一些列操作。 二、Java线程池类型 为了实现不同类型的线程池,定义了三个接口:Executor、 ExecutorService 、ScheduledExecutorService接口,他们都位于java.util.concurrent包下。 Executor:能够执行Runnable的简单接口,只含有execute一个方法 ExecutorService :继承自Executor,添加了shutdown、submit、invokeAll等方法,以支持异步执行 ScheduledExecutorService:继承自ExecutorService,支持周期性地执行任务 Java线程池目前有三个实现,分别是ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。ThreadPoolExecutor可以根据构造方法实现不同类型的线程池(或者使用Executors类),但是不支持定时任务;ScheduledExecutorService支持定时执行;ForkJoinPool利用Fork-join框架,实现了work-stealing,对于执行时间差异很大的任务使用该类型线程池能够实现负载均衡。图1展示了他们直接的继承/实现关系。 三、线程池参数与使用实例 下面我们分别介绍这三个线程池的构造方法,这样我们才能够知道如何创建线程池。因为每种线程池都有多个构造方法,我们只介绍参数最复杂的那一个。 1. ThreadPoolExecutor 构造方法 corePoolSize: 当线程池中线程数量小于corePoolSize, 即使这些线程是空闲的,也不会被销毁。但如果调用方法allowCoreThreadTimeOut(true),表示核心线程池的线程在没有任务到达的时候,keepAliveTime时间后销毁。 maximumPoolSize:限定线程池最大数量。当线程池中线程数达到corePoolSize时,且任务队列workQueue以及满了,就会创建新线程直到数量达到maxiumPoolSize。注意,如果workQueue是一个无界队列(unbounded)时,该参数是无效的,因为你无论添加多少任务workQueue都不会满。 keepAliveTime:当线程数大于corePoolSize时,多余线程的存活时间。 unit:keepAliveTime参数的时间单位 workQueue:用于存放尚未执行任务的队列,元素必须是Runnable。workQueue必须是一个阻塞队列,例如LinkedBlockingQueue、ArrayBlockingQueue。 threadFactory:创建线程时所使用的线程工厂,默认为Executors.defaultThreadFactor。如果你想让创建的线程带有自定义name或者优先级时,可以传入自己实现的线程工厂。 handler:在某种情况下(下面会提到),无法提交任务所执行的处理策略。目前有如下内置的handler实现。 AbortPolicy 直接拒绝新任务,并抛出RejectedExecutionException异常 CallerRunsPolicy 用当前线程的execute方法执行被拒绝的任务,如果执行器已经关闭则丢弃任务 DiscardPolicy 默默地丢弃新到的任务 DiscardOldestPolicy 丢弃最老的一个未执行的任务并执行当前任务 线程池逻辑 图2描述了线程创建与入队的逻辑。如果从线程创建和入队的角度分别观察,能够整理出如下规则。 线程创建规则 当线程池中线程数量小于corePoolSize时,将会创建新线程(即使之前创建当线程处于空闲) 如果线程数量大于等于corePoolSize且小于maximumPoolSize时,只有队列已满才会创建新线程 入队规则 如果线程数量小于corePoolSize,优先创建线程而不入队…

Read more