`
g21121
  • 浏览: 694577 次
  • 性别: 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并发编程实战源码,java并发编程实战pdf,Java

    《Java并发编程实战》是Java并发编程领域的一本经典著作,它深入浅出地介绍了如何在Java平台上进行高效的多线程编程。这本书的源码提供了丰富的示例,可以帮助读者更好地理解书中的理论知识并将其应用到实际项目中。...

    Java 并发编程实战.pdf

    《Java并发编程实战》这本书是关于Java语言中并发编程技术的经典著作。它详细介绍了如何在Java环境中有效地实现多线程程序和并发控制机制。在Java平台上,由于其本身提供了强大的并发编程支持,因此,掌握并发编程...

    java并发编程内部分享PPT

    Java并发编程是Java开发中的重要领域,特别是在多核处理器和分布式系统中,高效地利用并发可以极大地提升程序的性能和响应速度。这份“java并发编程内部分享PPT”显然是一个深入探讨这一主题的资料,旨在帮助开发者...

    java 并发编程的艺术pdf清晰完整版 源码

    《Java并发编程的艺术》这本书是Java开发者深入理解并发编程的重要参考书籍。这本书全面地介绍了Java平台上的并发和多线程编程技术,旨在帮助开发者解决在实际工作中遇到的并发问题,提高程序的性能和可伸缩性。 ...

    JAVA并发编程艺术pdf版

    《JAVA并发编程艺术》是Java开发者深入理解和掌握并发编程的一本重要著作,它涵盖了Java并发领域的核心概念和技术。这本书详细阐述了如何在多线程环境下有效地编写高效、可靠的代码,对于提升Java程序员的技能水平...

    java并发编程书籍

    Java并发编程是软件开发中的一个关键领域,尤其是在大型企业级应用和分布式系统中。通过学习相关的书籍,开发者可以深入理解如何有效地设计和实现高效的多线程应用程序,避免并发问题,如竞态条件、死锁、活锁等。...

    Java 并发核心编程

    #### 一、Java并发概述 自Java诞生之初,其设计者就赋予了该语言强大的并发处理能力。Java语言内置了对线程和锁的支持,这使得开发者能够轻松地编写多线程应用程序。本文旨在帮助Java开发者深入理解并发的核心概念...

    Java并发编程实践.pdf

    - **Executor框架**:是Java并发工具包中的核心组件之一,提供了强大的任务管理和执行功能,如定时任务、周期性任务等。 #### 二、Java线程安全问题及解决方案 ##### 2.1 线程安全问题分析 在多线程环境下,如果不...

    Java并发编程从入门到精通(pdf)(附源码)

    接着,书中将深入探讨Java并发工具类,如Executor框架、Semaphore信号量、CyclicBarrier和CountDownLatch等,这些工具在实际项目中有着广泛的应用,学习它们能帮助开发者更好地控制和协调并发任务。 此外,书中的...

    java并发编程

    Java并发编程是Java开发者必须掌握的关键技能之一,它涉及到如何在多线程环境中高效、安全地执行程序。并发编程能够充分利用多核处理器的计算能力,提高应用程序的响应速度和整体性能。《Java编程并发实战》这本书是...

    13-Java并发编程学习宝典.zip

    Java并发编程是软件开发中的重要领域,特别是在大型系统和高并发场景中不可或缺。"13-Java并发编程学习宝典.zip" 包含了一系列关于Java并发编程的学习资源,旨在帮助开发者掌握多线程编程的核心技术和最佳实践。以下...

    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框架深度解析与应用实践

    在现代Java应用开发中,多线程并发编程已成为提升程序性能的关键技术之一。Java通过引入Executor框架,为并发任务的执行提供了一种高效、灵活的管理机制。本文将深入探讨Executor框架的设计哲学、核心组件,并结合...

    《Java并发编程的艺术》

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

    java并发书籍xxxxxxx

    Java并发编程是Java开发中的重要领域,特别是在多核处理器和分布式系统中,高效地利用并发能力可以极大地提高程序性能和响应速度。以下是一些关于Java并发编程的关键知识点: 1. **线程与进程**:在操作系统中,...

    JAVA并发编程实践 中文 高清 带书签 完整版 Doug Lea .pdf

    根据提供的文件信息,“JAVA并发编程实践 中文 高清 带书签 完整版 Doug Lea .pdf”,我们可以推断出这份文档主要聚焦于Java并发编程的技术实践与理论探讨。下面将从多个角度来解析这个文档可能涵盖的关键知识点。 ...

    Java并发程序设计教程

    本教程将深入探讨Java并发编程的核心概念、最佳实践以及常见陷阱。 首先,我们要了解Java中的线程。线程是操作系统分配CPU时间的基本单元,Java通过Thread类来抽象线程。创建线程有两种方式:继承Thread类并重写run...

    [Java并发编程实践].(Java.Concurrency.in.Practice).Brian.Goetz.英文原版.pdf

    ### Java并发编程实践 #### 书籍概述 《Java并发编程实践》是一本由Brian Goetz等人编写的关于Java并发编程的经典著作。本书深入浅出地介绍了Java 5.0及之后版本中新增加的并发特性,并对并发编程进行了全面而详尽...

Global site tag (gtag.js) - Google Analytics