前言
线程并发系列文章:
Java 线程基础
Java 线程状态
Java “优雅”地中断线程-实践篇
Java “优雅”地中断线程-原理篇
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有误
Java Unsafe/CAS/LockSupport 应用与原理
Java 并发"锁"的本质(一步步实现锁)
Java Synchronized实现互斥之应用与源码初探
Java 对象头分析与使用(Synchronized相关)
Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程
Java Synchronized 重量级锁原理深入剖析上(互斥篇)
Java Synchronized 重量级锁原理深入剖析下(同步篇)
Java并发之 AQS 深入解析(上)
Java并发之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解
Java 并发之 ReentrantLock 深入分析(与Synchronized区别)
Java 并发之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)
最详细的图文解析Java各种锁(终极篇)
线程池必懂系列
线程池系列文章:
通常来说,开启线程能够提高程序的并发能力,而Thread 类里并没有任何方法可以获取到线程的执行结果。接下来,我们将一步步分析如何拿到线程的执行结果。
通过本篇文章,你将了解到:
1、原始方式 获取线程执行结果
2、FutureTask 获取线程执行结果
3、线程池 获取线程执行结果
1、原始方式 获取线程执行结果
public class ThreadRet {
private int sum = 0;
public static void main(String args[]) {
ThreadRet threadRet = new ThreadRet();
threadRet.startTest();
}
private void startTest() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
int a = 5;
int b = 5;
int c = a + b;
//将结果赋予成员变量
sum = c;
System.out.println("c:" + c);
}
});
t1.start();
try {
//等待线程执行完毕
t1.join();
//执行过这条语句后,说明线程已将sum赋值
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sum:" + sum);
}
}
打印结果如下:
说明主线程已经拿到线程1的执行结果了。
原理也很简单:
- 线程1在计算结果,那么其它线程必须要等待它执行结束了才能得到有效的结果。
- 此时可以选择两种方式检测计算结果:轮询与等待-通知,当然是用等待-通知更有效率。
- Thread.join 即是是用了等待-通知方式,Thread.join 一直等到目标线程执行完毕后才返回,否则阻塞等待。
Thread.join 原理请移步:Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解
2、FutureTask 获取线程执行结果
FutureTask 使用
虽然上述方式能够获取线程执行结果,然而却有如下不足之处:
1、每次都需要定义不同类型的成员变量来接收返回结果。
2、每次都需要Thread.join 阻塞等待。
想想有没有什么方法将上述功能封装起来呢?该到Callable出场了。
private void startCall() {
//定义Callable,具体的线程处理在call()里进行
Callable<String> callable = new Callable() {
@Override
public Object call() throws Exception {
String result = "hello world";
//返回result
return result;
}
};
//定义FutureTask,持有Callable 引用
FutureTask<String> futureTask = new FutureTask(callable);
//开启线程
new Thread(futureTask).start();
try {
//获取结果
String result = futureTask.get();
System.out.println("result:" + result);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
最终打印如下:
可以看出,能够正确获取到线程的执行结果了。
操作步骤分四步:
1、定义Callable,线程具体的工作在此处理,可以返回任意值。
2、定义FutureTask,持有Callable 引用,并且指定泛型的具体类型,该类型决定了线程最终的返回类型。实际上就是将Callable.call()返回值强转为具体类型。
3、最后构造Thread,并传入FutureTask,而FutureTask实现了Runnable。
4、通过FutureTask 获取线程执行结果。
FutureTask 原理
先看关键类的定义:
#Callable.java
public interface Callable<V> {
//返回泛型
V call() throws Exception;
}
Callable 只有一个方法,该方法返回泛型类型。
再看FutureTask:
#FutureTask.java
public void run() {
try {
//传进来的Callable
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//执行Callable call 方法
result = c.call();
ran = true;
} catch (Throwable ex) {
...
}
//记录结果
if (ran)
set(result);
}
} finally {
...
}
}
protected void set(V v) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
//记录到成员变量 outcome里
outcome = v;
//CAS 修改状态
U.putOrderedInt(this, STATE, NORMAL); // final state
//通知等待线程执行结果的其它线程
finishCompletion();
}
}
private void finishCompletion() {
//waiters 为链表头,该链表记录着所有等待该线程执行结果的其它线程
for (WaitNode q; (q = waiters) != null;) {
//CAS 不成功,则继续循环
if (U.compareAndSwapObject(this, WAITERS, q, null)) {
//CAS 修改成功,将链表头置空
//遍历链表
for (;;) {
//取出等待的线程
Thread t = q.thread;
if (t != null) {
q.thread = null;
//唤醒
LockSupport.unpark(t);
}
//继续找下一个线程
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
...
}
上述逻辑很清晰:
1、FutureTask 实现了Runnable,重写了run()方法,当线程执行时会执行run()方法,而run()最终调用了Callable的call()方法,返回值记录在成员变量outcome里。
2、当run()执行完毕后,说明结果已经出来了,将通知其它线程(唤醒)。
既然有唤醒过程,那么必然有等待过程,否则唤醒的逻辑无意义。
FutureTask 实现了Future接口,重写了get()等方法。
#FutureTask.java
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
//阻塞等待
s = awaitDone(false, 0L);
//处理返回值
return report(s);
}
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
WaitNode q = null;
boolean queued = false;
for (;;) {
//一些临界状态判断
//封装为节点,加入到等待链表里
//限时等待
else if (timed) {
...
if (state < COMPLETING)
//线程挂起指定的时间
LockSupport.parkNanos(this, parkNanos);
}
else
//一直等待,直到有结果返回
LockSupport.park(this);
}
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
//将强转为泛型指定的类型
return (V)x;
...
}
由上可以看出:
1、FutureTask.get() 阻塞等待线程执行结果返回。
2、若是还没结果,先将自己加入到等待链表里,并且可以指定等待一定的时间,若是时间到了还是没有结果,就直接返回。
3、最后等到执行结果后,强转为想要的类型,在例子里强转为String。
整个流程用图表示如下:
对比原始方式和FutureTask方式异同点:
不同点
原始方式通过Object.wait/Object.notify 来实现等待通知,而FutureTask 通过Volatile + CAS+LockSupport 来实现等待通知。
相同点
线程执行结果都存储在成员变量里。
3、线程池 获取线程执行结果
小Demo:
private void startPool() {
//线程池
ExecutorService service = Executors.newSingleThreadExecutor();
//定义Callable
Callable<String> callable = new Callable() {
@Override
public Object call() throws Exception {
String result = "hello world";
//返回result
return result;
}
};
//返回Future,实际上是FutureTask实例
Future<String> future = service.submit(callable);
try {
System.out.println(future.get());
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
线程池提供了三种方式获取线程执行结果,虽然使用方式不太一样,但内部都是依靠Callable+FutureTask来实现的。
第一种
Future submit(Callable task);
传入的参数为Callable,Callable.run()决定返回值。
第二种
Future<?> submit(Runnable task);
传入参数为Runnable,Runnable.run() 没有返回值,因此此时Future.get()返回null。
第三种
Future submit(Runnable task, T result);
传入参数除了Runnable,还有result,虽然Runnable.run() 没有返回值,但是最终Future.get() 将会返回result。
总结
以上分析了三种方式获取线程结果(实际两种,最后两种可归结为一类),虽然做法不一样,但速途同归。 想要获取线程执行结果,无非两个核心:
1、能够知道线程何时结束。
2、能够将结果抛出(比如存储在成员变量里)。
下篇将重点分析线程池的使用与原理。
演示代码 若是有帮助,给github 点个赞呗~