深入浅出Java Concurrency : 线程池 part 8 线程池的实现及原理 (
线程池任务执行结果
这一节来探讨下线程池中任务执行的结果以及如何阻塞线程、取消任务等等。
1 package info.imxylz.study.concurrency.future;
2
3 public class SleepForResultDemo implements Runnable {
4
5 static boolean result = false;
6
7 static void sleepWhile(long ms) {
8 try {
9 Thread.sleep(ms);
10 } catch (Exception e) {}
11 }
12
13 @Override
14 public void run() {
15 //do work
16 System.out.println("Hello, sleep a while.");
17 sleepWhile(2000L);
18 result = true;
19 }
20
21 public static void main(String[] args) {
22 SleepForResultDemo demo = new SleepForResultDemo();
23 Thread t = new Thread(demo);
24 t.start();
25 sleepWhile(3000L);
26 System.out.println(result);
27 }
28
29 }
30
在没有线程池的时代里面,使用Thread.sleep(long)去获取线程执行完毕的场景很多。显然这种方式很笨拙,他需要你事先知道任务可能的执行时间,并且还会阻塞主线程,不管任务有没有执行完毕。1 package info.imxylz.study.concurrency.future;
2
3 public class SleepLoopForResultDemo implements Runnable {
4
5 boolean result = false;
6
7 volatile boolean finished = false;
8
9 static void sleepWhile(long ms) {
10 try {
11 Thread.sleep(ms);
12 } catch (Exception e) {}
13 }
14
15 @Override
16 public void run() {
17 //do work
18 try {
19 System.out.println("Hello, sleep a while.");
20 sleepWhile(2000L);
21 result = true;
22 } finally {
23 finished = true;
24 }
25 }
26
27 public static void main(String[] args) {
28 SleepLoopForResultDemo demo = new SleepLoopForResultDemo();
29 Thread t = new Thread(demo);
30 t.start();
31 while (!demo.finished) {
32 sleepWhile(10L);
33 }
34 System.out.println(demo.result);
35 }
36
37 }
38
使用volatile与while死循环的好处就是等待的时间可以稍微小一点,但是依然有CPU负载高并且阻塞主线程的问题。最简单的降低CPU负载的方式就是使用Thread.join().SleepLoopForResultDemo demo = new SleepLoopForResultDemo();
Thread t = new Thread(demo);
t.start();
t.join();
System.out.println(demo.result);
显然这也是一种不错的方式,另外还有自己写锁使用wait/notify的方式。其实join()从本质上讲就是利用while和wait来实现的。上面的方式中都存在一个问题,那就是会阻塞主线程并且任务不能被取消。为了解决这个问题,线程池中提供了一个Future接口。
在Future接口中提供了5个方法。
V get() throws InterruptedException, ExecutionException: 等待计算完成,然后获取其结果。
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException。最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
boolean cancel(boolean mayInterruptIfRunning):试图取消对此任务的执行。
boolean isCancelled():如果在任务正常完成前将其取消,则返回 true。
boolean isDone():如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。
API看起来容易,来研究下异常吧。get()请求获取一个结果会阻塞当前进程,并且可能抛出以下三种异常:InterruptedException:执行任务的线程被中断则会抛出此异常,此时不能知道任务是否执行完毕,因此其结果是无用的,必须处理此异常。
ExecutionException:任务执行过程中(Runnable#run())方法可能抛出RuntimeException,如果提交的是一个java.util.concurrent.Callable<V>接口任务,那么java.util.concurrent.Callable.call()方法有可能抛出任意异常。
CancellationException:实际上get()方法还可能抛出一个CancellationException的RuntimeException,也就是任务被取消了但是依然去获取结果。
对于get(long timeout, TimeUnit unit)而言,除了get()方法的异常外,由于有超时机制,因此还可能得到一个TimeoutException。boolean cancel(boolean mayInterruptIfRunning)方法比较复杂,各种情况比较多:
如果任务已经执行完毕,那么返回false。
如果任务已经取消,那么返回false。
循环直到设置任务为取消状态,对于未启动的任务将永远不再执行,对于正在运行的任务,将根据mayInterruptIfRunning是否中断其运行,如果不中断那么任务将继续运行直到结束。
此方法返回后任务要么处于运行结束状态,要么处于取消状态。isDone()将永远返回true,如果cancel()方法返回true,isCancelled()始终返回true。
来看看Future接口的实现类java.util.concurrent.FutureTask<V>具体是如何操作的。在FutureTask中使用了一个AQS数据结构来完成各种状态以及加锁、阻塞的实现。
在此AQS类java.util.concurrent.FutureTask.Sync中一个任务用4中状态:
初始情况下任务状态state=0,任务执行(innerRun)后状态变为运行状态RUNNING(state=1),执行完毕后变成运行结束状态RAN(state=2)。任务在初始状态或者执行状态被取消后就变为状态CANCELLED(state=4)。AQS最擅长无锁情况下处理几种简单的状态变更的。
void innerRun() {
if (!compareAndSetState(0, RUNNING))
return;
&nb
补充:软件开发 , Java ,