Future是用来异步获取值的。线程或者Runnable的run方法没有返回值,所以在实际项目中需要有返回值的异步场景下,就需要用Future来获取异步任务执行后的返回值。Future是一个接口,我们一般用的实现类有三种FutrueTask、ForkJoinTask和CompletableFuture。
一 FutureTask
1.1 用法
如以下代码,使用的是FutureTask,主线程会一直等待,直到Future返回:
package com.youngthing.threads.future;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* Future demo
* created at 15/03/2022
*
* @author 花书粉丝
* <a href="mailto://yujianbo@chtwm.com">yujianbo@chtwm.com</a>
* @since 1.0.0
*/
public class FutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Long> future = new FutureTask<Long>(()-> {
Thread.sleep(1000);
return System.currentTimeMillis();
});
new Thread(future).start();
final Long aLong = future.get();
System.out.println(aLong);
}
}
另外一个用法就是
package com.youngthing.threads.future;
import java.util.concurrent.*;
/**
* Future demo
* created at 15/03/2022
*
* @author 花书粉丝
* <a href="mailto://yujianbo@chtwm.com">yujianbo@chtwm.com</a>
* @since 1.0.0
*/
public class FuturePoolDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
final ExecutorService executorService = Executors.newCachedThreadPool();
final Future<String> future = executorService.submit(() -> {
Thread.sleep(1000);
return "时间 : " + System.currentTimeMillis();
});
System.out.println(future.get());
executorService.shutdown();
}
}
但是这个要注意,执行完毕了,需要关闭executor service。否则线程会一直wait。
现在面试必卷原理。而且JDK的Future,因为使用了一个大名鼎鼎的算法Treiber stack,所以更容易问到。但是我们只关注两点,一是状态变化,二是Treiber栈。
1.2 状态转换
FutureTask内部有个状态字段state。
因为状态不可逆,所以这是个一次性任务,在实际应用中最好不要多个线程去执行同一个FutureTask。
1.3 Treiber stack等待与唤醒
Treiber stack是一个无锁栈,物理结构是一个单向链表,其入栈和出栈均使用CAS实现。而CAS的实现也有所不同,JDK8使用的是Unsafe,而JDK9使用的是VarHandler。其他线程调用get时会把线程加入到这个等待栈。然后调用LockSupport.park将线程进入等待状态,如以下代码。
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// The code below is very delicate, to achieve these goals:
// - call nanoTime exactly once for each call to park
// - if nanos <= 0L, return promptly without allocation or nanoTime
// - if nanos == Long.MIN_VALUE, don't underflow
// - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
// and we suffer a spurious wakeup, we will do no worse than
// to park-spin for a while
long startTime = 0L; // Special value 0L means not yet parked
WaitNode q = null;
boolean queued = false;
for (;;) {
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING)
// We may have already promised (via isDone) that we are done
// so never return empty-handed or throw InterruptedException
Thread.yield();
else if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
else if (q == null) {
if (timed && nanos <= 0L)
return s;
q = new WaitNode();
}
else if (!queued)
queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
else if (timed) {
final long parkNanos;
if (startTime == 0L) { // first time
startTime = System.nanoTime();
if (startTime == 0L)
startTime = 1L;
parkNanos = nanos;
} else {
long elapsed = System.nanoTime() - startTime;
if (elapsed >= nanos) {
removeWaiter(q);
return state;
}
parkNanos = nanos - elapsed;
}
// nanoTime may be slow; recheck before parking
if (state < COMPLETING)
LockSupport.parkNanos(this, parkNanos);
}
else
LockSupport.park(this);
}
}
任务完成后,遍历整个等待栈,调用LockSupport.unpark将线程挨个唤醒。
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (WAITERS.weakCompareAndSet(this, q, null)) {
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;
}
}
done();
callable = null; // to reduce footprint
}
二 ForkJoinTask
ForkJoinTask的实现类一般用RecursiveTask,以下是我不用线程池的代码示例:
package com.youngthing.juc.future;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RecursiveTask;
/**
* 12/5/2022 2:49 PM 创建
*
* @author 花书粉丝
*/
public class ForkJoinFutureDemo {
public static void main(String[] args) {
final int[] ints = new int[100];
for (int i = 0; i < ints.length; i++) {
ints[i] = i + 1;
}
final SumArray sumArray = new SumArray(ints, 0, ints.length - 1);
sumArray.fork();
try {
System.out.println("总结果:"+sumArray.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
private static class SumArray extends RecursiveTask<Integer> {
private int[] array;
private int startIndex;
private int endIndex;
public SumArray(int[] array, int startIndex, int endIndex) {
this.array = array;
this.startIndex = startIndex;
this.endIndex = endIndex;
}
/**
* The main computation performed by this task.
*
* @return the result of the computation
*/
@Override
protected Integer compute() {
if (endIndex == startIndex) {
System.out.println(Thread.currentThread().getName()+"计算一个结果");
return array[startIndex];
}
if (endIndex - startIndex == 1) {
System.out.println(Thread.currentThread().getName()+"计算两项之和");
return array[startIndex] + array[endIndex];
}
int midIndex = (startIndex + endIndex) / 2;
final SumArray task1 = new SumArray(array, startIndex, midIndex);
final SumArray task2 = new SumArray(array, midIndex + 1, endIndex);
task1.fork();
task2.fork();
System.out.println(Thread.currentThread().getName()+"拆分任务");
return task1.join() + task2.join();
}
}
}
由于我在中间打印了线程名称,所以可以看到是用了多个线程池的,以下是部分输出日志:
ForkJoinPool.commonPool-worker-7计算一个结果
ForkJoinPool.commonPool-worker-30计算两项之和
ForkJoinPool.commonPool-worker-2计算一个结果
ForkJoinPool.commonPool-worker-8拆分任务
ForkJoinPool.commonPool-worker-20计算两项之和
总结果:5050
不用ForkJoinPool的结果就是线程会创建得太多了。所以尽量使用ForkJoinPool来管理ForkJoinTask.
三 CompletableFuture
FutureTask是普通的异步获取返回值,ForkJoinTask是拆分任务合并结果。而含有依赖的任务是ForkJoinTask不能解决的,所以JDK推出了CompletableFuture,CompletableFuture用一个重要的thenApply方法,处理上一个任务的结果。我只简单写个配合JAVA8 Stream API的例子:
package com.youngthing.juc.future;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
/**
* 12/5/2022 4:27 PM 创建
*
* @author 花书粉丝
*/
public class CompletionFutureDemo {
public static void main(String[] args) {
Integer[] array = {1, 2, 3, 4};
Arrays.stream(array)
.map(x -> CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
return x * x;
}))
.map(x->x.thenApply(n->n+1))
.map(CompletableFuture::join).forEach(x -> {
System.out.println(x);
});
}
}
输出结果如下:
ForkJoinPool.commonPool-worker-9
2
ForkJoinPool.commonPool-worker-9
5
ForkJoinPool.commonPool-worker-9
10
ForkJoinPool.commonPool-worker-9
17
在我的例子里,任务是计算 x 2 + 1 x^2+1 x2+1,但是我分成了两步,第一步是计算 x 2 x^2 x2,计算结果传入下一步,最终的结果是计算 x 2 + 1 x^2+1 x2+1。这行代码就是关键:
map(x->x.thenApply(n->n+1))