2 Future


  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))
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醒过来摸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值