JUC包之Future模式

Future模式

Future模式是多线程开发中的一种常见的设计模式,核心思想异步调用,让串行化的问题变得并行处理节省时间。

当程序执行一个任务时,这个任务可能执行的很慢,它不可能立即返回结果,但可以返回一个契约,因此我们可以在该任务执行的时候,再去执行其它任务,最终用该契约获取结果。

举个栗子:

在网上买了一部手机,手机三天后才会到货,但会马上产生一个订单,这个订单就是上述所提到的契约,然后我们不用一直干等手机的到来,完全可以去忙别的事,当快递到来的时候,订单核对一下,然后就获得了最终结果。

JDK中的Future模式

Future接口类似于之前的契约,根据Future对象调用get方法最终获取到结果。

FutureTask接口实现Callable接口对象到Runnable接口对象的过渡,最终会交由Callable接口完成,Callable接口的call方法返回最终结果。

FutureTask类说明

英文有一定障碍的看中文注释

/**
一、修订说明:

1、这与该类以前依赖AbstractQueuedSynchronizer的版本不同,
主要是为了避免用户在取消竞争期间意外地保留中断状态。

2、当前设计中的同步控制依赖于通过CAS更新的“state”字段来跟踪完成情况,
以及一个用于保存等待线程的简单Treiber堆栈。

二、说明:

1、与往常一样,我们绕过了使用atomicxfielddupdater的开销,而是直接使用不安全的内部函数。

任务状态:
一、状态说明
1、此任务的运行状态,最初为新建。
2、 运行状态仅在方法set、setException和cancel中转换为终端状态。
3、在完成过程中,状态可能会呈现完成(在设置结果时)或中断(仅在中断转轮以满足取消(true))的瞬态值。
4、从这些中间状态到最终状态的转换使用更便宜的顺序/延迟写入,因为值是唯一的,无法进一步修改。

二、可能的状态转换:

1、新建->完成->正常
2、新建->完成->异常
3、新建->取消
4、新建->中断->中断
**/

/**
 * A cancellable asynchronous computation.  This class provides a base
 * implementation of {@link Future}, with methods to start and cancel
 * a computation, query to see if the computation is complete, and
 * retrieve the result of the computation.  The result can only be
 * retrieved when the computation has completed; the {@code get}
 * methods will block if the computation has not yet completed.  Once
 * the computation has completed, the computation cannot be restarted
 * or cancelled (unless the computation is invoked using
 * {@link #runAndReset}).
 *
 * 

A {@code FutureTask} can be used to wrap a {@link Callable} or * {@link Runnable} object. Because {@code FutureTask} implements * {@code Runnable}, a {@code FutureTask} can be submitted to an * {@link Executor} for execution. * *

In addition to serving as a standalone class, this class provides * {@code protected} functionality that may be useful when creating * customized task classes. * * @since 1.5 * @author Doug Lea * @param The result type returned by this FutureTask's {@code get} methods */ public class FutureTask implements RunnableFuture { /* * Revision notes: This differs from previous versions of this * class that relied on AbstractQueuedSynchronizer, mainly to * avoid surprising users about retaining interrupt status during * cancellation races. Sync control in the current design relies * on a "state" field updated via CAS to track completion, along * with a simple Treiber stack to hold waiting threads. * * Style note: As usual, we bypass overhead of using * AtomicXFieldUpdaters and instead directly use Unsafe intrinsics. */ /** * The run state of this task, initially NEW. The run state * transitions to a terminal state only in methods set, * setException, and cancel. During completion, state may take on * transient values of COMPLETING (while outcome is being set) or * INTERRUPTING (only while interrupting the runner to satisfy a * cancel(true)). Transitions from these intermediate to final * states use cheaper ordered/lazy writes because values are unique * and cannot be further modified. * * Possible state transitions: * NEW -> COMPLETING -> NORMAL * NEW -> COMPLETING -> EXCEPTIONAL * NEW -> CANCELLED * NEW -> INTERRUPTING -> INTERRUPTED */ private volatile int state; private static final int NEW = 0; private static final int COMPLETING = 1; private static final int NORMAL = 2; private static final int EXCEPTIONAL = 3; private static final int CANCELLED = 4; private static final int INTERRUPTING = 5; private static final int INTERRUPTED = 6; /** The underlying callable; nulled out after running */ private Callable callable; /** The result to return or exception to throw from get() */ private Object outcome; // non-volatile, protected by state reads/writes /** The thread running the callable; CASed during run() */ private volatile Thread runner; /** Treiber stack of waiting threads */ private volatile WaitNode waiters; // 内部类 WaitNode // 在Treiber堆栈中记录等待线程的简单链表节点。 //有关更详细的说明,请参见其他类,如Phaser和SynchronousQueue。 /** * Simple linked list nodes to record waiting threads in a Treiber * stack. See other classes such as Phaser and SynchronousQueue * for more detailed explanation. */ static final class WaitNode { volatile Thread thread; volatile WaitNode next; WaitNode() { thread = Thread.currentThread(); } }

简单使用

get方法

package com.github.excelent01;

import java.util.concurrent.*;

/**
 * @auther plg
 * @date 2019/5/17 16:54
 */
public class TestFuture {
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        Future future =  service.submit(()->{
            TimeUnit.SECONDS.sleep(10); // 模拟延时
            return 10;
        });
        //==============================
        System.out.println("do other works.");
        //==============================

        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        service.shutdown();

    }
}

Future 接口API

public interface Future {

    /**
     * 用来取消任务,取消成功则返回true,取消失败则返回false。
     * mayInterruptIfRunning参数表示是否允许取消正在执行却没有执行完毕的任务,设为true,则表示可以取消正在执行过程中的任务。
     * 如果任务已完成,则无论mayInterruptIfRunning为true还是false,此方法都返回false,即如果取消已经完成的任务会返回false;
     * 如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;
     * 如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
     */
    boolean cancel(boolean mayInterruptIfRunning);

    /**
     * 表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回true
     */
    boolean isCancelled();

    /**
     * 表示任务是否已经完成,若任务完成,则返回true
     */
    boolean isDone();

    /**
     * 获取执行结果,如果最终结果还没得出该方法会产生阻塞,直到任务执行完毕返回结果
     */
    V get() throws InterruptedException, ExecutionException;

    /**
     * 获取执行结果,如果在指定时间内,还没获取到结果,则抛出TimeoutException
     */
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

Future就是对于Runnable或Callable任务的执行进行查询、中断任务、获取结果。

因为烧水要花费15min,因此没有必要白白干等,浪费时间,在这个时间段内,可以去完成准备茶具的一些工作,等茶具准备好之后,就静等水烧开了。因此这是一个典型的依靠Future模式来解决的问题。

代码实现:

package Future;

import java.util.Scanner;
import java.util.concurrent.*;

/**
 * @auther plg
 * @date 2019/5/17 17:54
 */
public class TeaTest2 {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        BoilWater boilWater = new BoilWater();
        FutureTask futureTask1 = new FutureTask<>(boilWater);
        ReadyTeaSet readyTeaSet = new ReadyTeaSet(futureTask1);
        FutureTask futureTask2 = new FutureTask<>(readyTeaSet);
        new Thread(futureTask1).start();
        Thread.sleep(2000);
        new Thread(futureTask2).start();
        System.out.println(futureTask2.get());

    }
}
// T1 线程
class  BoilWater implements Callable {
    @Override
    public String call() throws Exception {
        System.out.println("T1: 洗水壶");
        Thread.sleep(1000);
        System.out.println("T1: 烧水");
        Thread.sleep(10000);
        return "T1: 水烧开了。";
    }
}
// T2 线程ReadyTeaSet
class ReadyTeaSet implements Callable{
    private FutureTask futureTask = null;
    public ReadyTeaSet(FutureTask futureTask) {
        this.futureTask = futureTask;
    }

    @Override
    public String call() throws Exception {
        System.out.println("T2: 洗水杯");
        Thread.sleep(1000);
        System.out.println("T2: 洗茶壶");
        Thread.sleep(2000);
        System.out.println("T2: 取茶叶");
        Thread.sleep(1000);
        System.out.println("T2: 等着水烧开。");
        System.out.println(futureTask.get());
        return "一壶好茶.";
    }
}

运行结果:
T1: 洗水壶
T1: 烧水
T2: 洗水杯
T2: 洗茶壶
T2: 取茶叶
T2: 等着水烧开。
T1: 水烧开了。
一壶好茶.

Process finished with exit code 0

普通模式与Future模式的简单对比:

  1. 普通模式在处理多任务时是串行的,在遇到耗时操作的时候只能等待,直到阻塞被解除,才会继续执行下一个任务
  2. Future模式,只是发起了耗时操作,函数立马就返回了,真正执行具体操作由另外一个工作线程去完成,并不会阻塞客户端线程。

所以在工作线程执行耗时操作的时候客户端无需等待,可以继续做其他事情,等到需要的时候再向工作线程获取结果。

Future模式详解:

1、Future模式是多线程设计常用的一种设计模式。

2、它的核心思想是异步调用。

对于Future模式来说,它无法立即返回你需要的数据,但是它会返回一个契约,将来你可以凭借这个契约去获取你需要的信息。

Future模式可以简单理解成:我有一个任务,它比较耗时,但是我又不想一直空等,而且有时候任务的结果并不立刻需要,于是我把这任务提交给了Future,Future替我完成这个任务,同时Future将这个任务订单的信息返回给我。

那么我就可以不用等了,自己可以去做任何想做的事情。

当我需要这个任务结果的时候,我可以根据返回的订单信息,尝试从Future那里去取出该任务的结果(当然如果此时还未完成,则会阻塞)。

当然,考虑到此时任务可能还未完成,Future也支持任务是否完成检测,由此,我们可以根据是否完成设计不同的应当逻辑。

FutureTask

说完Future,Future因为是接口不能直接用来创建对象,就有了下面的FutureTask。

先看看FutureTask的实现:

可以看到FutureTask类实现了RunnableFuture接口,接着看RunnableFuture接口源码:


public interface RunnableFuture extends Runnable, Future {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

可以看到RunnableFuture接口继承了Runnable接口和Future接口,也就是说其实FutureTask既可以作为Runnable被线程执行,也可以作为Future得到Callable的返回值。

手动实现Future模式:

下面的DataFuture类只是一个包装类,创建它时无需阻塞等待。

在工作线程准备好数据后使用setRealData方法将数据传入。

客户端只要在真正需要数据时调用getRealData方法即可,如果此时数据已准备好则立即返回,否则getRealData方法就会等待,直到获取数据完成。

DataFuture

public class DataFuture {

    private T realData;
    private boolean isOK = false;

    public synchronized T getRealData() {
        while (!isOK) {
            try {
                // 数据未准备好则等待
                wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return realData;
    }

    public synchronized void setRealData(T data) {
        isOK = true;
        realData = data;
        notifyAll();
    }

}

下面实现一服务端,客户端向服务端请求数据时,服务端并不会立刻去加载真正数据,只是创建一个DataFuture,创建子线程去加载真正数据,服务端直接返回DataFuture即可。

Server

import java.util.concurrent.Executors;

public class Server {

    public DataFuture getData() {
        final DataFuture data = new DataFuture<>();

        Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                data.setRealData("最终数据");
            }
        });
        return data;
    }
}

测试代码

客户端调用 代码如下:

TestDataFuture



public class TestDataFuture {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        Server server = new Server();
        DataFuture dataFuture = server.getData();

        try {
            // 先执行其他操作
            Thread.sleep(5000);
            // 模拟耗时...
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("结果数据:" + dataFuture.getRealData());
        System.out.println("耗时: " + (System.currentTimeMillis() - start));
    }
}

测试结果

结果数据:最终数据
耗时: 5006

Process finished with exit code 0

Future不足之处

上面说了一堆Future的好处,那么就没有缺点吗?

上面例子可以看到使用Future模式比传统模式效率明显提高了,使用Future一定程度上可以让一个线程池内的任务异步执行;

但同时也有个明显的缺点:

就是回调无法放到与任务不同的线程中执行,传统回调最大的问题就是不能将控制流分离到不同的事件处理器中。

比如主线程要等各个异步执行线程返回的结果来做下一步操作,就必须阻塞在future.get()方法等待结果返回,这时其实又是同步了,如果遇到某个线程执行时间太长时,那情况就更糟了。

到Java8时引入了一个新的实现类CompletableFuture,弥补了上面的缺点,在下篇会讲解CompletableFuture的使用。

关键词:java培训

你可能感兴趣的:(juc)