线程池的概念
线程池(Thread Pool)是一种基于池化思想管理线程的工具,经常出现在多线程服务器中,如MySQL,ElasticSearch等等
线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:
- 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
- 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
- 系统无法合理管理内部的资源分布,会降低系统的稳定性。
为解决资源分配这个问题,线程池采用了“池化”(Pooling)思想。池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。
ThreadPoolExecutor
线程池实现类ThreadPoolExecutor是Executor框架最核心的类。
ThreadPoolExecutor 类中提供的四个构造方法。
我们来看最长的那个,其余三个都是在这个构造方法的基础上产生(其他几个构造方法说白点都是给定某些默认参数的构造方法比如默认制定拒绝策略是什么)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
ThreadPoolExecutor 3 个最重要的参数:
- corePoolSize: 核心线程数线程数定义了最小可以同时运行的线程数量。
- maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
- workQueue:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
ThreadPoolExecutor其他常见参数:
- keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁;
- unit :keepAliveTime 参数的时间单位。
- threadFactory :executor 创建新线程的时候会用到。
- handler :饱和策略。如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时,ThreadPoolTaskExecutor 定义一些策略:
1. ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。
2. ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策略。
3. ThreadPoolExecutor.DiscardPolicy: 不处理新任务,直接丢弃掉。
4. ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理的任务请求。
ThreadPoolExecutor的运行机制
任务task与工作线程worker
了解下面的内容前,我们需要了解下任务和工作线程在线程池中的定义。
任务task
我们在使用ThreadPoolExecutor时,一般使用submit方法提交我们要执行的callable或者runable方法,任务task即是一个callable或者runable基础上封装的future。
1
2
3
4
5
6
7
8
9
10
11/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; }
工作线程worker
workder是线程池中封装好的一个runnable实现类,包含了一个thread,和runable(即是我们传入的callble或者runable)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/** Thread this worker is running in. Null if factory fails. */ final Thread thread; /** Initial task to run. Possibly null. */ Runnable firstTask; /** Per-thread task counter */ volatile long completedTasks; /** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) */ Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } /** Delegates main run loop to outer runWorker */ public void run() { runWorker(this); }
重写的run()方法,调用了ThreadPoolExecutor中的runworker()方法。
1. 任务调度
当我们调用线程池的submit方法时,线程池是怎么调度这些任务的呢?
看看这块代码吧!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } else if (!addWorker(command, false)) reject(command);
我把这份代码抽象成流程图就是这样。
你们可能会有疑问,代码中并没有看到 线程数对于核心线程数 和最大线程数的判断?这块儿在哪里实现的,不要着急答案在addWorker()方法中,在3.工作线程的增加我们会详细的解释,addWorker()方法()和excute()方法共同实现了线程中任务的调度策略。
引申:代码中ctl.get()用于获取线程池的状态,但它是怎样做到并发情况下,且不加锁情况下的并发一致性的??
2. 任务缓冲
上诉代码中我们有看到
1
2workQueue.offer(command)
这就是线程池中管理我们提交的任务的核心部分,workQueue 是线程池中的一个BlockingQueue阻塞队列,线程池是以生产者消费者模式,通过这个阻塞队列来实现任务的缓冲。BlockingQueue的方法描述
方法处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove() | poll() | take() | poll(time,unit) |
检查方法 | element() | peek() | - | - |
使用不同的BlockingQueue可以实现不一样的任务存取缓冲策略,在这里,我们先介绍下阻塞队列的不同的具体实现类
3. 工作线程的增加
废话不说,先看代码,firstTask即是我们提交的任务,core指是否核心线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted; }
引申:代码中有出现ReentrantLock (可重入锁),可重入锁相较于synchronized有什么好处呢?
4. 工作线程如何执行任务
当启动工作线程之后,worker就开始执行任务了。作为新建的工作线程,如果fisrtTask不为空,会立刻执行这个任务;如果为空,则会调用getTask()在阻塞队列中不停的申请任务。而fisrtTask是否为空,取决于excute()方法中 的addWorker传参。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
5. 任务的申请
getTask()方法描述了 任务的申请策略,核心线程与非核心线程的任务申请是不一样的,主要是通过阻塞队列的take()和poll(TimeUnit timeOut)作区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
6. 工作线程的回收
在任务申请失败或者runWorker发生异常时,会在finally代码块中执行一个processWorkerExit()方法。
进入到这个方法,当前线程会被百分百的销毁,但线程的总数可能会不变?为啥呢?因为当前线程销毁过后,能还会新加入一个没有初始任务的线程到线程池内。但取决于核心线程池数和是否允许核心线程超时allowCoreThreadTimeOut。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37private void processWorkerExit(Worker w, boolean completedAbruptly) { // 正常的话再runWorker的getTask方法workerCount已经被减一了 if (completedAbruptly) decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 累加线程的completedTasks completedTaskCount += w.completedTasks; // 从线程池中移除超时或者出现异常的线程 workers.remove(w); } finally { mainLock.unlock(); } // 尝试停止线程池 tryTerminate(); int c = ctl.get(); // runState为RUNNING或SHUTDOWN if (runStateLessThan(c, STOP)) { // 线程不是异常结束 if (!completedAbruptly) { // 线程池最小空闲数,允许core thread超时就是0,否则就是corePoolSize int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // 如果min == 0但是队列不为空要保证有1个线程来执行队列中的任务 if (min == 0 && !workQueue.isEmpty()) min = 1; // 线程池还不为空那就不用担心了 if (workerCountOf(c) >= min) return; // replacement not needed } // 1.线程异常退出 // 2.线程池为空,但是队列中还有任务没执行,看addWoker方法对这种情况的处理 addWorker(null, false); } }
有个很关键的参数allowCoreThreadTimeOut,一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉,如果设置allowCoreThreadTimeOut = true,则会作用于核心线程。我们来看看 allowCoreThreadTimeOut true 和false情况下,核心线程和非核心线程的销毁状况。
情况一:allowCoreThreadTimeOut=false
当前线程数小于核心线程数时,会使用take()从队列中获取任务,这是个阻塞方法。
这就意味着此时核心线程用于不会被销毁(除非出现异常情况不得不退出),但即便异常退出了,到了processWorkerExit里,在线程池没被销毁的情况下,也会新建一个线程做为补充。
1
2当前线程数大于核心线程数时,会使用poll()从队列中获取任务,这不是阻塞方法,有最大等待时长。
超过时间就会进入processWorkerExit,如果allowCoreThreadTimeOut=false,线程会被销毁掉。
情况二:allowCoreThreadTimeOut=true
不管超没超过核心线程数限制, 使用poll方法拉取任务,超时后进入processWorkerExit,当前线程会被销毁!!!但如果是线程池中最后一个队列,且还有任务未完成,会新建一个线程作为补充
综上。allowCoreThreadTimeOut达到的效果就是为false时,线程池里即便核心线程都空闲,也会保留;但如果为true,核心线程在执行完任务过后就会被销毁。
1
2所以呢,allowCoreThreadTimeOut在线程池的某些构造函数中是默认为false,因为一旦设置为true,核心线程就失去了本来的节省创建线程开销的意义
Executors
创建线程池,除了new ThreadPoolExecutor()之外,还可以使用jdk预先设置的几种线程池,
但是我们和<阿里巴巴java开发手册>不推荐使用这些线程池,他们都有各自严重的缺陷,下面会详细的表达。
Executors包含四种线程池:FixedThreadPool、 SingleThreadExecutor 、CachedThreadPool、 ScheduledThreadPool
FixedThreadPool:
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
1
2
3
4
5
6public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
- 该线程池的最大线程数等于核心线程数,所以在默认情况下,该线程池的线程不会因为闲置状态超时而被销毁。
- 如果当前线程数小于核心线程数,并且也有闲置线程的时候提交了任务,这时也不会去复用之前的闲置线程,会创建新的线程去执行任务。如果当前执行任务数大于了核心线程数,大于的部分就会进入队列等待。等着有闲置的线程来执行这个任务。
SingleThreadExecutor:
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } static class FinalizableDelegatedExecutorService extends DelegatedExecutorService { FinalizableDelegatedExecutorService(ExecutorService executor) { super(executor); } protected void finalize() { super.shutdown(); } }
该方法创建了一个核心线程数和最大线程数为1,任务队列为Integer.MAX_VALUE的FinalizableDelegatedExecutorService线程池。该线程池是对ExecutorService的一个封装,实现了finalize方法,保证在FinalizableDelegatedExecutorService对象被回收时线程池能被关闭(shutdown)。
- 有且仅有一个工作线程执行任务
- 所有任务按照指定顺序执行,即遵循队列的入队出队规则
CachedThreadPool:
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
1
2
3
4
5
6public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
该方法创建了一个核心线程数为0,最大线程数为Integer.MAX_VALUE的线程池。
SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作put必须等待消费者的移除操作take,反过来也一样。SynchronousQueue内部并没有数据缓存空间,你不能调用peek()方法来看队列中是否有数据元素,因为数据元素只有当你试着取走的时候才可能存在,不取走而只想偷窥一下是不行的,当然遍历这个队列的操作也是不允许的。队列头元素是第一个排队要插入数据的线程,而不是要交换的数据。数据是在配对的生产者和消费者线程之间直接传递的,并不会将数据缓冲数据到队列中。可以这样来理解:生产者和消费者互相等待对方,握手,然后一起离开。
执行execute方法时,首先会先执行SynchronousQueue的offer方法提交任务,并查询线程池中是否有空闲线程来执行SynchronousQueue的poll方法来移除任务。如果有,则配对成功,将任务交给这个空闲线程。否则,配对失败,创建新的线程去处理任务;当线程池中的线程空闲时,会执行SynchronousQueue的poll方法等待执行SynchronousQueue中新提交的任务。若超过60s依然没有任务提交到SynchronousQueue,这个空闲线程就会终止;因为maximumPoolSize是无界的,所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程;每次提交任务,都会立即有线程去处理,因此CachedThreadPool适用于处理大量、耗时少的任务。
- 这种线程池内部没有核心线程,线程的数量是有没限制的。
- 在创建任务时,若有空闲的线程时则复用空闲的线程,若没有则新建线程。
- 没有工作的线程(闲置状态)在超过了60S还不做事,就会销毁。
ScheduledThreadPool:
创建一个定长线程池,支持定时及周期性任务执行。
1
2
3
4
5
6
7
8
9public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public ScheduledThreadPoolExecutor(int corePoolSize) { //super对应的是ThreadPoolExecutor super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue()); }
该方法创建了一个核心线程数为corePoolSize,最大线程数为Integer.MAX_VALUE,任务队列为无限大的ScheduledThreadPoolExecutor 线程池。
DelayQueue 封装了一个 PriorityQueue,PriorityQueue 会对队列中的任务进行排序,执行所需时间短的放在前面先被执行(ScheduledFutureTask 的 time 变量小的先执行),如果执行所需时间相同则先提交的任务将被先执行(ScheduledFutureTask 的 squenceNumber 变量小的先执行)。
- 不仅设置了核心线程数,最大线程数也是Integer.MAX_VALUE。
- 这个线程池是上述4个中为唯一个有延迟执行和周期执行任务的线程池。
使用注意事项
fixedThreadPool和singleThreadExecutor:线程数大小指定,任务队列大小不限,容易内存溢出
cachedThreadPool和scheduledThreadPool: 最大线程数为Integer.MAX_VALUE,容易资源耗尽
使用线程池时,因为核心线程一经创建,就不会被销毁(除非shutdown线程池)。
So ,不推荐使用上述的现成的线程池工厂,而通过自己的业务场景去new ThreadPoolExecutor()
线程池在业务中该怎么去运用?
1. 业务场景
场景1:快速响应用户请求
用户发起的实时请求,服务追求响应时间。比如说用户要查看一个商品的信息,那么我们需要将商品维度的一系列信息如商品的价格、优惠、库存、图片等等聚合起来,展示给用户。
从用户体验角度看,这个结果响应的越快越好,如果一个页面半天都刷不出,用户可能就放弃查看这个商品了。而面向用户的功能聚合通常非常复杂,伴随着调用与调用之间的级联、多级级联等情况,业务开发同学往往会选择使用线程池这种简单的方式,将调用封装成任务并行的执行,缩短总体响应时间。另外,使用线程池也是有考量的,这种场景最重要的就是获取最大的响应速度去满足用户,所以应该不设置队列去缓冲并发任务,调高corePoolSize和maxPoolSize去尽可能创造多的线程快速执行任务
场景2:离线批处理请求
离线的大量计算任务,需要快速执行。比如说,统计某个报表,需要计算出全国各个门店中有哪些商品有某种属性,用于后续营销策略的分析,那么我们需要查询全国所有门店中的所有商品,并且记录具有某属性的商品,然后快速生成报表。
这种场景需要执行大量的任务,我们也会希望任务执行的越快越好。这种情况下,也应该使用多线程策略,并行计算。但与响应速度优先的场景区别在于,这类场景任务量巨大,并不需要瞬时的完成,而是关注如何使用有限的资源,尽可能在单位时间内处理更多的任务,也就是吞吐量优先的问题。所以应该设置队列去缓冲并发任务,调整合适的corePoolSize去设置处理任务的线程数。在这里,设置的线程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低吞吐量。
2. 实践中的问题
线程池使用面临的核心的问题在于:线程池的参数并不好配置。一方面线程池的运行机制不是很好理解,配置合理需要强依赖开发人员的个人经验和知识;另一方面,线程池执行的情况和任务类型相关性较大,IO密集型和CPU密集型的任务运行起来的情况差异非常大,这导致业界并没有一些成熟的经验策略帮助开发人员参考。
那有没有一个公式可以供我们参考设置线程池的参数?
3. 最佳的解决方案
答案就是根据实时监控线上运行的线程池状态(拒绝的任务数,任务的排队等候时间,队列的长度等),根据监控告警动态的调整线程池的各项参数。
ThreadPoolExecutor提供了这些公共的get方法可以查看线程池的运行状态
我们再来看看ElasticSearch的监控,是不是略有所思啊。
这里面好像没有RejectCount,也就是队列拒绝的任务数量,我们要怎么获取呢?
首先自定义你的RejectedExecutionHandler
1
2
3
4
5
6
7
8
9
10
11
12
13public class MyRejectHandler implements RejectedExecutionHandler { public AtomicInteger rejectCount = new AtomicInteger(0); @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { rejectCount.addAndGet(1); throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + executor.toString()); } }
然后就可以顺利拿到rehectCount啦
1
2
3
4
5
6
7MyRejectHandler rejectHandler = new MyRejectHandler(); ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 5, 60, TimeUnit.SECONDS, new ArrayBlockingQueue <Runnable>(10), rejectHandler); System.out.println("核心线程数:" + poolExecutor.getCorePoolSize() + "拒绝任务数量" + rejectHandler.rejectCount.get()+ "队列任务数"+poolExecutor.getQueue().size());
Future
cancel:
1
2boolean cancel(boolean mayInterruptIfRunning);
mayInterruptIfRunning设置为true,可以打断运行中的线程,返回是否取消成功。
isCancelled:
1
2boolean isCancelled();
返回该线程是否在完成之前被打断。
isDone:
1
2boolean isDone();
返回该线程是否完成。
get:
1
2V get() throws InterruptedException, ExecutionException;
阻塞的方式获取任务执行的结果。
1
2
3V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
阻塞的方式获取任务执行的结果,但是只等待指定的时间,如果超过该时间未返回则跑出TimeoutException。
CompletionService:
CompletionService接口定义了一组任务管理接口,ExecutorCompletionService实现该接口并对Executor进行了一次包装。
ExecutorCompletionService使用了自己定义的RunnableFuture类,重写了FutureTask的done方法,将执行完成的任务的结果直接存入阻塞队列BlockingQueue completionQueue中 ,所以其可以直接获取到已经执行完成任务的结果,解决了Future获取结果只能随机获取的问题。
CompletionService包含以下方法
submit:
1
2
3
4
5//提交一个Callable类型的任务 Future<V> submit(Callable<V> task); //提交一个Runnable类型的任务,并指定一个返回结果result Future<V> submit(Runnable task, V result);
take:
1
2
3//阻塞的方式获取执行结果,若一直没有任务结果返回,则一直阻塞 Future<V> take() throws InterruptedException;
poll:
1
2
3
4
5//获取completionQueue中的结果,如结果为空则返回null Future<V> poll(); //等待指定时间的方式获取completionQueue中的结果,如超过指定时间则返回null Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
CompletableFuture:
CompletableFuture是java8新增的一个Future的实现类,其相对于FutureTask增加了回调的功能。示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22@Test public void name() throws InterruptedException { CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{ System.out.println("开始:" + System.currentTimeMillis()); sleep(5); System.out.println("结束:" + System.currentTimeMillis()); return 1; }).whenComplete((p,e) -> { System.out.println("完成后任务开始:" + System.currentTimeMillis()); System.out.println("任务结果:" + p +"任务异常:" + e); }); Thread.currentThread().join(); } private void sleep(long seconds) { try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e) { e.printStackTrace(); } }
执行结果如下:
最后
以上就是幸福楼房最近收集整理的关于深入浅出java线程池的全部内容,更多相关深入浅出java线程池内容请搜索靠谱客的其他文章。
发表评论 取消回复