`
g21121
  • 浏览: 685602 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java并发之Executor

 
阅读更多

        在很多系统中都会出现以下这种情况:某些对象或资源不得不频繁的去创建和使用,而这些对象和资源一般都是一次性的,也就是使用完就只能等待垃圾收集器的清理。等到下一次使用时又会往复这样的过程,一旦这种创建销毁的过程累积到一定程度,就会给系统带来性能乃至稳定性方面的问题。在设计模式中有一种叫做“享元模式”的设计模式,

        享元模式(Flyweight Pattern)使用共享物件,用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。通常物件中的部分状态是可以分享。常见做法是把它们放在外部数据结构,当需要使用时再将它们传递给享元。

        设想一下我们在餐厅中吃饭,餐厅很大,来来往往的人也非常多。当客人需要服务的时候只需要呼唤服务员就可以了,把事情交代给他,他就会为你服务。如果这个服务员忙不开,就会有另一个服务员为你服务。

       线程的使用就会遇到这种重复创建销毁的情况,如果提供一种机制:当我需要线程(服务员)为我工作的时候,我呼唤一声,无论是马上还是过一会,最终都会有一个线程(服务员)到来,为自己服务。线程池就是根据这种道理来动态的为使用者提供线程的支持。

        线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

 

        一、线程池基础

        1.线程池的构成

        Java线程池一般由四个部分组成:

        1)线程池管理器(Executor):用于创建并管理线程池,Java中 Executor的实现者为ThreadPoolExecutor。

        2)工作线程(Worker):线程池中线程,任务的实际执行者。

        3)任务接口(Task):线程执行任务,每一个外部提交至线程池的工作都是一个task。

        4)任务队列(Queue):用于存放 task的队列。



        其中红色是正在工作的Worker,绿色是闲置的Worker

 

        2.线程池的种类

        Java中提供了几种常用的线程池种类,它们应用于不同的使用场景。

        1)CachedThreadPool():一个可缓存的线程池,此种线程池内部的 Worker线程数量不固定,可以根据task数目情况灵活的处理Worker线程,调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则该线程池会自动创建新的 Worker线程以供调用(Worker线程最多为Integer.MAX_VALUE)。当 Worker线程空闲超过指定时间(默认60秒)则会终止并从缓存中移除那些已有 60 秒钟未被使用的线程。对于执行很多短期异步任务的程序而言,这种线程池通常可提高程序性能。

        CachedThreadPool最大的特点就是灵活,可以根据任务的繁忙情况自动的调整,从而使得任务能够即使的处理。缺点就是在大批量任务到来的时候会占用较多的系统资源。CachedThreadPool比较适合多而短的任务类型。

        在Java中使用线程池一般通过 Executors类来进行创建,创建成功后利用execute(Runnable r)来执行任务:

ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute(new Thread());

        2)FixedThreadPool(int count):一个可重用固定 Worker线程数的线程池,以共享的无界队列方式来运行这些线程。FixedThreadPool是一个固定数量Worker的线程池,无论 task的多少线程池中 Worker数都不会变化,当新的 task到来时,如果池中有空闲线程,那么 task就会被分配给空闲的 Worker线程。如果没有空闲的 Worker线程,task任务就只能等待,直到有 Worker线程被释放。

        在创建线程池的时候需要指定Worker的数量:

ExecutorService threadPool = Executors.newFixedThreadPool(10);
threadPool.execute(new Thread());

        3)ScheduledThreadPool(int corePoolSize):一个定长线程池,它可以支持定时和周期性任务的执行。 

ExecutorService threadPool = Executors.newScheduledThreadPool(10);
threadPool.execute(new Thread());

        ScheduledThreadPool适合于延迟或定时执行的任务。

        4)SingleThreadExecutor():一个单线程执行程序,因为只有一个线程,所以这里已经不能称之为“池”了。它可安排在给定延迟后运行命令或者定期地执行。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程会代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

ExecutorService threadPool = Executors.newSingleThreadExecutor();
threadPool.execute(new Thread());

        5)SingleThreadScheduledExecutor():一个单线程执行程序,它可以支持定时和周期性任务的执行。 

ExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
threadPool.execute(new Thread());

 

        3.线程池相关接口

        创建新的线程池一般都会使用 Executors这个类,Executors定义了 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和众多实用方法。可以说 Executors是一个线程池的工具类,通过它可以实现线程池的诸多功能。



 
        在 java.util.concurrent包中定义了三个Executor接口:

          Executor,一个运行新任务的简单接口。
          ExecutorService,扩展了Executor接口。添加了一些用来管理执行器生命周期和任务生命周期的方法。
          ScheduledExecutorService,扩展了ExecutorService。支持Future和定期执行任务。

        1)Executor

        Executor接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。通常使用 Executor并不是显式地创建线程。如,new Thread(new(RunnableTask())).start(),而是使用类似以下方式:

Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
...

        Executor接口只有一个方法 execute(Runnable command),execute方法用来执行线程任务。Executor 接口并没有严格地要求执行是异步的。在最简单的情况下,执行程序可以在调用者的线程中立即运行已提交的任务:

public class DirectExecutor implements Executor {
	public void execute(Runnable r) {
		r.run();
	}
}

        更常见的是,任务是在某个不是调用者线程的线程中执行的。以下执行程序将为每个任务生成一个新线程。

public class ThreadPerTaskExecutor implements Executor {
	public void execute(Runnable r) {
		new Thread(r).start();
	}
}

        然而这些实现都是最简单也是最无用的,更复杂和高级的实现像ThreadPoolExecutor 类提供一个可扩展的线程池实现。Executors 类为这些 Executor 提供了便捷的工厂方法。

        所以 Executor接口更像是一种规范和设计思路。

        2)ExecutorService

        ExecutorService接口继承自Executor,ExecutorService接口在提供了execute方法的同时,新加了更加通用的submit方法。submit方法除了和 execute方法一样可以接受 Runnable对象作为参数,还可以接受 Callable对象作为参数。使用 Callable对象可以能使任务返回执行的结果。通过 submit方法返回的 Future对象可以读取 Callable任务的执行结果,或是管理 Callable任务和 Runnable任务的状态。 ExecutorService也提供了批量运行 Callable任务的方法。最后,ExecutorService还提供了一些关闭执行器的方法。如果需要支持即时关闭,执行器所执行的任务需要正确处理中断。 

        ExecutorService在Executor的基础上添加了很多实用的特性及方法,是Executor的重要扩展。

        3)ScheduledExecutorService

        ScheduledExecutorService接口继承自ExecutorService,ScheduledExecutorService扩展ExecutorService接口并添加了schedule方法。调用 schedule方法可以在指定的延时后执行一个 Runnable或者 Callable任务。ScheduledExecutorService接口还定义了按照指定时间间隔定期执行任务的 scheduleAtFixedRate方法和 scheduleWithFixedDelay方法。

        所有的 schedule 方法都接受相对延迟和周期时间作为参数,而不是绝对的时间或日期。将以 Date所表示的绝对时间转换成要求的形式很容易。例如,要安排在某个以后的 Date运行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)。但是要注意,由于网络时间同步协议、时钟漂移或其他因素的存在,因此相对延迟的期满日期不必与启用任务的当前 Date 相符。 Executors 类为此包中所提供的 ScheduledExecutorService 实现提供了便捷的工厂方法。 

        Executor作为顶级接口,它提供了方式与方法。ExecutorService对Executor实现了扩展,而ScheduledExecutorService就规定了更具体的类型。

 

        4.Executor相关实现

        接下来就是相关 Executor的实现类,其中ThreadPoolExecutor最为重要,所以我们要重点学习。

        Executor有三个实现类:AbstractExecutorService, ScheduledThreadPoolExecutor, ThreadPoolExecutor

       1)AbstractExecutorService

        AbstractExecutorService是一个抽象类,它提供了 ExecutorService 执行方法的默认实现。此类使用 newTaskFor 返回的 RunnableFuture 实现 submit、invokeAny 和 invokeAll 方法,默认情况下,RunnableFuture 是此包中提供的 FutureTask 类。例如,submit(Runnable) 的实现创建了一个关联 RunnableFuture 类,该类将被执行并返回。子类可以重写 newTaskFor 方法,以返回 FutureTask 之外的 RunnableFuture 实现。

        2)ThreadPoolExecutor

        ThreadPoolExecutor是最具体的线程池 Executor实现,ThreadPoolExecutor继承自 AbstractExecutorService类,并提供了4个构造方法。

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) 
      
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) 
 
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) 

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 

        其中前3个都是调用第4个构造器进行的初始化工作。在创建ThreadPoolExecutor实例时需要为相关参数赋值,以下是各种属性的具体意义。

        (1)核心池与最大池大小

        corePoolSize代表核心池大小,maximumPoolSize代码最大池大小。ThreadPoolExecutor 将根据 corePoolSize和 maximumPoolSize设置的边界自动调整池大小。

        在创建了线程池后,默认情况下,线程池中并没有任何 Worker线程,线程池中的线程数为0,而是等待有任务到来时才会创建线程去执行任务,这时一种懒初始化的策略,但是如果调用了prestartAllCoreThreads()或者prestartCoreThread()方法,可以预创建线程,即在没有任务到来之前就创建一个或corePoolSize个线程。

        当新任务在方法 execute() 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(一种夸张说法,最大也就 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。 

        (2)保持活动时间

        keepAliveTime表示线程保持活动时间,意思是说线程在空闲状态下可以存活多长时间。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,如果此时池中当前有多于 corePoolSize的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会被终止。这是一种当池处于非活动状态时减少资源消耗的方法。当线程池中的线程数小于corePoolSize时,keepAliveTime无效。可以使用方法 setKeepAliveTime(long timeUnit) 动态地更改此参数。如果使用了allowCoreThreadTimeOut(true)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用(为了避免连续线程替换,保持活动时间在设置为 true 时必须大于 0),直到线程池中的线程数为0。

        (3)时间单位

        TimeUnit是时间单位,参数keepAliveTime的时间单位,是一个枚举类型。TimeUnit 不维护时间信息,但是有助于组织和使用可能跨各种上下文单独维护的时间表示形式。毫微秒定义为千分之一微秒,微秒为千分之一毫秒,毫秒为千分之一秒,一分钟为六十秒,一小时为六十分钟,一天为二十四小时。 

        TimeUnit 主要用于通知基于时间的方法如何解释给定的计时参数。例如,如果 lock 不可用,则以下代码将在 50 毫秒后超时: 

Lock lock = ...;
if ( lock.tryLock(50L, TimeUnit.MILLISECONDS) ) ...

        TimeUnit定义了7种时间维度类型:

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

        (4)任务队列

        workQueue代表了任务队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响。ThreadPoolExecutor采用的是BlockingQueue(阻塞式队列),一般来说,阻塞式队列可以选择:ArrayBlockingQueue, DelayQueue, LinkedBlockingDeque, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue这几种。

        所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互: 

        如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。 

         如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。 

         如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。 

        排队有三种通用策略: 

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

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

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

        (5)线程工厂

        使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread 返回 null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。 

        (6)被拒绝的任务

        RejectedExecutionHandler表示当拒绝处理任务时的策略。

        当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(Runnable r) 中提交的新任务将被拒绝。在以上两种情况下,execute 方法都将调用其 RejectedExecutionHandler 的 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。下面是四种预定义的处理程序策略: 

        • ThreadPoolExecutor.AbortPolicy(默认值):处理程序遭到拒绝将抛出运行时 RejectedExecutionException异常。 

        • ThreadPoolExecutor.CallerRunsPolicy:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。 

        • ThreadPoolExecutor.DiscardPolicy:不能执行的任务将被删除。 

        • ThreadPoolExecutor.DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。 

        定义和使用其他种类的 RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。 

        以下是 ThreadPoolExecutor类的所有重要成员变量:

//任务缓存队列,用来存放等待执行的任务
private final BlockingQueue<Runnable> workQueue;
//线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
private final ReentrantLock mainLock = new ReentrantLock(); 
//用来存放 Worker的Set集合  
private final HashSet<Worker> workers = new HashSet<Worker>();  
//线程保持活动时间
private volatile long  keepAliveTime; 
 //如果为 false (默认值)核心线程不会因为空闲而销毁,如果为true核心线程如果空闲时间超过keepAliveTime也会被销毁
private volatile boolean allowCoreThreadTimeOut;  
//核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   corePoolSize;     
//线程池最大线程数
private volatile int   maximumPoolSize;   
//线程池中当前的线程数
private volatile int   poolSize; 
//任务拒绝策略
private volatile RejectedExecutionHandler handler; 
//线程工厂,用来创建线程
private volatile ThreadFactory threadFactory;  
//用来记录线程池中曾经出现过的最大线程数
private int largestPoolSize;   
//用来记录已经执行完毕的任务个数
private long completedTaskCount;  

 

        二、线程池原理

        1.线程池的状态

        线程池有4种状态,在 ThreadPoolExecutor类中用

volatile int runState;

        来表示线程池状态,其中volatile 所修饰的变量对所有线程是立即可见的,volatile变量所有的写操作都能立即反应到其他线程之中,换句话说 volatile变量在各个线程中是一致的。

        runState有4种状态,分别是:

        1)RUNNING:接受新任务并处理队列中的任务

        2)SHUTDOWN:不会接受新的任务,但是会处理已队列中的任务

        3)STOP:不会接受新的任务,也不会处理已队列中的任务,并中断正在进行的任务

        4)TERMINATED:与STOP相同, 所有线程都已经终止

        当创建线程池后,初始时,线程池处于 RUNNING状态;

        如果调用了 shutdown()方法,则线程池处于 SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;

        如果调用了 shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;

        当线程池处于 SHUTDOWN或 STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为 TERMINATED状态。

        这四种状态间的转换关系是:

        1)RUNNING -> SHUTDOWN:调用shutdown()方法

        2)(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow()方法

        3)SHUTDOWN -> TERMINATED:队列和池都为空的情况下

        4)STOP -> TERMINATED:池为空时



 

        2.线程池执行流程

        1)创建线程池

        创建线程池之前已经提到过,一般通过 Executors类的相关方法创建不同类型的线程池,从Executors的源代码中可以观察到,虽然创建的线程池类型不同,但创建的方法基本都相同,都是利用不同参数的组合来创建一个 ThreadPoolExecutor类实例,比如:

//创建固定大小线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
//创建单Worker线程池
public static ExecutorService newSingleThreadExecutor() {
	return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
//创建可缓存线程池
public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}

        其他的创建方法也基本类似,这些不同类型的线程池虽然彼此有诸多不同,但归根结底他们却都是ThreadPoolExecutor的实例,所以ThreadPoolExecutor类的掌握就异常重要。

        2)初始化

        之前已经说过,默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。但在实际中如果需要线程池创建之后就立即创建线程,可以通过prestartCoreThread()与prestartAllCoreThreads()方法两个方法办到。

          prestartCoreThread():初始化一个核心线程;

          prestartAllCoreThreads():初始化所有核心线程

   下面是这2个方法的实现:

public boolean prestartCoreThread() {
	return addIfUnderCorePoolSize(null);
}

public int prestartAllCoreThreads() {
	int n = 0;
	while (addIfUnderCorePoolSize(null))
		++n;
	return n;
}

        其实它们都是调用的 addIfUnderCorePoolSize方法来增加Worker线程,这里之所以传值为 null,是因为没有实际任务,最后执行线程会阻塞在getTask方法中的r = workQueue.take();,即等待任务队列中有任务。

        3)执行任务

        在 ThreadPoolExecutor类中,使用 execute(Runnable command)方法提交任务,executor方法在将来某个时间执行给定任务。可以在新线程中或者在现有池线程中执行该任务。 如果无法将任务提交执行,或者因为此执行程序已关闭,或者因为已达到其容量,则该任务由当前 RejectedExecutionHandler处理。 

        以下是 execute方法的源代码:

public void execute(Runnable command) {
	//1.首先判断任务是否有效
    if (command == null)
        throw new NullPointerException();
    //2.判断Worker数是否不小于核心线程数,如果Worker不足则增加Worker
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
    	//3.如果线程池正在运行就将任务加入到队列中
        if (runState == RUNNING && workQueue.offer(command)) {
        	//继续确认线程池状态和Worker数
            if (runState != RUNNING || poolSize == 0)
            	//4.再次确认各种状态并对已排队的任务做相应处理
                ensureQueuedTaskHandled(command);
        }
        //添加任务失败时的处理
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); 
    }
}

        execute方法并不复杂,却包含了非常多的确认操作。

        (1)首先,判断提交的任务command是否为null,若是null,则抛出空指针异常;

        (2)随后,判断当前线程数是否不小于核心线程数,之后执行 addIfUnderCorePoolSize(command)方法。

        (3)然后,判断线程池状态是否为正在运行,并将任务添加到队列中。

        (4)最后,再次判断线程池的状态,这句判断是为了防止在将此任务添加进任务队列的同时其他线程突然调用shutdown()或者 shutdownNow()方法关闭了线程池的一种应急措施。如果是这样就执行ensureQueuedTaskHandled方法,对任务进行处理。

        (5)如果有不符合判断的地方就调用 addIfUnderMaximumPoolSize和 reject方法对任务进行拒绝处理。

        在 execute用到了几个重要的方法。

        (1) addIfUnderCorePoolSize方法

private boolean addIfUnderCorePoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < corePoolSize && runState == RUNNING)
            t = addThread(firstTask);
    } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}

        当前Worker数(poolSize)小于核心线程数(corePoolSize)时,execute方法会调用此方法。方法中首先会获得锁,然后再次进行判断,因为已经获得了锁,其他线程提交的任务将被控制,所以此时的判断时准确的。符合判断条件就调用addThread(firstTask)方法。addThread方法也非常关键,传进去的参数为要提交的任务,返回值为Thread类型。然后接着在下面判断 t是否为空,为空则表明创建线程失败(即poolSize>=corePoolSize或者runState不等于RUNNING),否则调用t.start()方法启动线程。

        addIfUnderMaximumPoolSize与 addIfUnderCorePoolSize类似,这里就不赘述了。

        (2)addThread方法

private Thread addThread(Runnable firstTask) {
    Worker w = new Worker(firstTask);
    Thread t = threadFactory.newThread(w);
    if (t != null) {
        w.thread = t;
        workers.add(w);
        int nt = ++poolSize;
        if (nt > largestPoolSize)
            largestPoolSize = nt;
    }
    return t;
}

        addThread方法的作用时创建一个新的 Worker并将任务分配给它。方法中,首先创建一个新的Worker并将任务赋予此 Worker。然后创建一个新的Worker线程执行任务,如果创建成功,则将创建的线程的引用赋予w的 thread成员变量。最后将此 Worker加入到Worker集合中并改变poolSize值。

        (3)ensureQueuedTaskHandled方法

private void ensureQueuedTaskHandled(Runnable command) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    boolean reject = false;
    Thread t = null;
    try {
        int state = runState;
        if (state != RUNNING && workQueue.remove(command))
            reject = true;
        else if (state < STOP &&
                 poolSize < Math.max(corePoolSize, 1) &&
                 !workQueue.isEmpty())
            t = addThread(null);
    } finally {
        mainLock.unlock();
    }
    if (reject)
        reject(command);
    else if (t != null)
        t.start();
}

        ensureQueuedTaskHandled方法作用是确保已经排队的任务的执行。其中大部分代码都与前面的差不多,唯独这段判断比较特别:

else if (state < STOP && poolSize < Math.max(corePoolSize, 1) && !workQueue.isEmpty())
    t = addThread(null);

        首先STOP的状态值前面已经说过了,是2,所以小于2的状态有RUNNING和 SHUTDOWN。

        然后判断当前线程数小于 corePoolSize与1中两者的最大值。(Math.max(int a, int b)的作用是返回a,b两者的最大值)

        最后判断工作队列是否为非空,调用 addThread(null)添加线程,这里之所以为null,在addThread内部执行了下面三步:

1.Worker w = new Worker(null);
2.Thread t = threadFactory.newThread(w);
3.return t;//这里的t与代码中t.start()的t是一致的。

        所以,t.start()实际上执行的是Worker内部的run方法。run()内部会在if条件里面使用“短路”:判断firstTask是否为 null,若不是 null则直接执行 firstTask的 run方法;如果是 null,则调用 getTask()方法来获取 Runnable类型实例。从哪里获取呢?workQueue!在execute方法中,执行ensureQueuedTaskHandled(command)之前就已经把 Runnable类型实例放入到 workQueue中了,所以这里可以从 workQueue中获取到。

        也就是说处于RUNNING与SHUTDOWN状态,并且当前线程数(poolSize)为0或小于核心线程数,工作队列不为空的情况下说明还有任务没有完成。除非addThread失败,否则至少会有一个活的线程来处理此任务。

        (4)reject方法

void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}

        reject方法的代码更简单,就是调用handler的rejectedExecution方法进行任务拒绝处理。

        总结:

        (1)如果当前线程池中的线程数目小于 corePoolSize时,每来一个任务,就会创建一个线程去执行这个任务;

        (2)如果当前线程池中的线程数目大于等于 corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;

        (3)如果当前线程池中的线程数目达到 maximumPoolSize,则会采取任务拒绝策略进行处理;

        (4)如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

1
4
分享到:
评论

相关推荐

    Java并发编程实战

    1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 1.2.3 异步事件的简化处理 1.2.4 响应更灵敏的用户界面 1.3 线程带来的风险 1.3.1 安全性问题 1.3.2 活跃性问题 1.3.3 ...

    《Java并发编程的艺术》

    《Java并发编程的艺术》内容涵盖Java并发编程机制的底层实现原理、Java内存模型、Java并发编程基础、Java中的锁、并发容器和框架、原子类、并发工具类、线程池、Executor框架等主题,每个主题都做了深入的讲解,同时...

    Java并发之线程池Executor框架的深入理解

    主要介绍了Java并发之线程池Executor框架的深入理解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    《Java并发编程的艺术》源代码

    Java并发编程的艺术 作者:方腾飞 魏鹏 程晓明 著 丛书名:Java核心技术系列 出版日期 :2015-07-25 ISBN:978-7-111-50824-3 第1章介绍Java并发编程的挑战,向读者说明进入并发编程的世界可能会遇到哪些问题,以及如何...

    Java-Executor并发框架.docx

    Java是天生就支持并发的语言,支持并发意味着多线程,线程的频繁创建在高并发及大数据量是非常消耗资源的,因为java提供了线程池。这篇文章主要介绍下并发包下的Executor接口,Executor接口虽然作为一个非常旧的接口...

    Java并发编程的艺术

    , 《Java并发编程的艺术》内容涵盖Java并发编程机制的底层实现原理、Java内存模型、Java并发编程基础、Java中的锁、并发容器和框架、原子类、并发工具类、线程池、Executor框架等主题,每个主题都做了深入的讲解,...

    JavaExecutor并发框架.pdf

    JavaExecutor并发框架.pdf

    java并发编程:Executor、Executors、ExecutorService.docx

    Executor: 一个接口,其定义了一个接收Runnable对象的方法executor,其方法签名为executor(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类,一般来说,...

    Java并发编程实践 PDF 高清版

    Java 5以及6在开发并发程序取得了显著的进步,提高了Java虚拟机的性能,提高了并发类的可伸缩性,并加入了丰富的新并发构建块。在本书中,这些便利工具的创造者不仅解释了它们究竟如何工作、如何使用,同时,还阐释...

    龙果java并发编程完整视频

    第53节Executor框架详解00:36:54分钟 | 第54节实战:简易web服务器(一)00:55:34分钟 | 第55节实战:简易web服务器(二)00:24:36分钟 | 第56节JDK8的新增原子操作类LongAddr原理与使用00:17:45分钟 | 第57节...

    Java 并发编程实战

    1.1 并发简史 1.2 线程的优势 1.2.1 发挥多处理器的强大能力 1.2.2 建模的简单性 1.2.3 异步事件的简化处理 1.2.4 响应更灵敏的用户界面 1.3 线程带来的风险 1.3.1 安全性问题 1.3.2 活跃性问题 1.3.3 ...

    Java并发编程原理与实战

    Executor框架详解.mp4 实战:简易web服务器(一).mp4 实战:简易web服务器(二).mp4 JDK8的新增原子操作类LongAddr原理与使用.mp4 JDK8新增锁StampedLock详解.mp4 重排序问题.mp4 happens-before简单概述.mp4 锁的...

    龙果 java并发编程原理实战

    龙果 java并发编程原理实战 第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四...

    Java并发Executor框架

     调用关系:Java线程一对一映射到本地操作系统的系统线程,当多线程程序分解若干任务,使用用户级的调度器(Executor框架)将任务映射为固定数量的线程,底层,操作系统吧、内核将这些线程映射到硬件处理器上。...

    并发编程实践,全面介绍基础知识、JVM同步原语、线程安全、低级并发工具、线程安全容器、高级线程协作工具、Executor部分等

    详细介绍java并发编程相关知识: 基础知识   并发与并行   Java并发演进历史   Java并发模型   线程模型   存储模型 JVM同步原语 volatile CAS 线程安全   保护“共享数据” 低级并发工具   原子变量   锁...

    Java并发编程的艺术_非扫描

    Java并发编程的艺术_非扫描本书特色本书结合JDK的源码介绍了Java并发框架、线程池的实现原理,帮助读者做到知其所以然。本书对原理的剖析不仅仅局限于Java层面,而是深入到JVM,甚至CPU层面来进行讲解,帮助读者从更...

Global site tag (gtag.js) - Google Analytics