多线程设计模式:Future设计模式

1 引入

假设有个任务需要执行比较长的的时间,通常需要等待任务执行结束或者出错才能返回结果,在此期间调用者只能陷入阻塞苦苦等待,对此,Future设计模式提供了一种凭据式的解决方案。。在我们日常生活中,关于凭据的使用非常多见,比如你去某西服手工作坊想订做一身合体修身的西服,西服的制作过程比较漫长,少则一个礼拜,多则一个月,你不可能一直待在原地等待,一般来说作坊会为你开一个凭据,此凭据就是Future,在接下来的任意日子里你可以凭借此凭据到作坊获取西服。

自JDK1.5起,Java提供了比较强大的Future接口,在JDK1.8时更是引入了CompletableFuture,其结合函数式接口可实现更强大的功能

2 简单实现

2.1 接口定义

2.1.1 Future接口设计

Future提供了获取计算结果和判断任务是否完成的两个接口,其中获取计算结果将会导致调用阻塞(在任务还未完成的情况下)

/**
 * @author wyaoyao
 * @date 2021/4/10 11:53
 * Future提供了获取计算结果和判断任务是否完成的两个接口,其中获取计算结果将会导致调用阻塞(在任务还未完成的情况下)
 * 其中泛型为结果的类型
 */
public interface Future<T> {

    /***
     * 返回任务执行结果
     * @return
     * @throws InterruptedException
     */
    T get() throws InterruptedException;

    /**
     * 是否任务完成
     * @return
     */
    boolean done();
}

2.1.2 Runnbale接口替代接口:Task接口

Task接口主要是提供给调用者实现计算逻辑之用的,
可以接受一个参数并且返回最终的计算结果,这一点非常类似于JDK1.5中的Callable接口。

Runnable接口本身是不支持返回值和形参的,所以定义此接口替代Runnable接口

/**
 * @author wyaoyao
 * @date 2021/4/10 11:58
 * Task接口主要是提供给调用者实现计算逻辑之用的,
 * 可以接受一个参数并且返回最终的计算结果,这一点非常类似于JDK1.5中的Callable接口
 */
@FunctionalInterface
public interface Task<I,R> {

    /**
     * 根据输入返回计算结果(Runnable接口本身是不支持返回值和形参的,所以定义此接口替代Runnable接口)
     *
     * @param input
     * @return
     */
    R get(I input);
}
2.1.3 FutureService接口
  • FutureService主要用于提交任务,提交的任务主要有两种,第一种不需要返回值,第二种则需要获得最终的计算结果
  • JDK 8中不仅支持default方法还支持静态方法,JDK 9甚至还支持接口私有方法
/**
 * @author wyaoyao
 * @date 2021/4/10 11:55
 * FutureService主要用于提交任务,提交的任务主要有两种,第一种不需要返回值,第二种则需要获得最终的计算结果
 * 。FutureService接口中提供了对FutureServiceImpl构建的工厂方法,
 * JDK 8中不仅支持default方法还支持静态方法,JDK 9甚至还支持接口私有方法
 */
public interface FutureService<I,R> {

    /**
     * 提交不需要返回值任务
     * @param runnable
     * @return
     */
    Future<?> submit(Runnable runnable);

    /**
     * 提交需要返回值的任务,其中Task接口代替了Runnable接口
     * @param task
     * @param input
     * @return
     */
    Future<R> submit(Task<I,R> task, I input);

}

2.2 接口实现

2.2.1 Future接口实现: FutureTask
public class FutureTask<T> implements Future<T> {
    /**
     * 存储任务执行结果
     */
    private T result;

    /**
     * 任务执行是否完成
     */
    private boolean isDone = false;

    /**
     * 定义一把锁
     */
    private final Object LOCK = new Object();

    @Override
    public T get() throws InterruptedException {
        synchronized (LOCK) {
            while (!isDone) {
                // 如果任务没有执行完成,则等待任务执行完成
                // 何时唤醒:当任务执行完成:参见下面的finish方法
                LOCK.wait();
            }
        }
        return result;
    }

    @Override
    public boolean done() {
        return this.isDone;
    }

    /**
     * finish 方法用于设置任务执行结果
     *
     * @param result
     */
    protected void finish(T result) {
        // 多线程下,需要加锁
        synchronized (LOCK) {
            if (isDone) {
                return;
            }
            // 完成那就设置结果,并更新isDone为true
            this.result = result;
            this.isDone = true;
            // 可能等待获取结果的线程,需要唤醒这些线程
            LOCK.notifyAll();
        }
    }
}

FutureTask中充分利用了线程间的通信wait和notifyAll,当任务没有被完成之前通过get方法获取结果,调用者会进入阻塞,直到任务完成并接收到其他线程的唤醒信号,finish方法接收到了任务完成通知,唤醒了因调用get而进入阻塞的线程。

2.2.2 FutureService实现

该实现类主要所用就是当提交任务之后,创建一个线程去执行该任务。

public class FutureServiceImpl<I, R> implements FutureService<I, R> {

    /**
     * 指定线程名字的前缀
     */
    private final static String THREAD_PREFIX = "FUTURE_THREAD";

    /**
     * 累加器
     */
    private final AtomicInteger nextCounter = new AtomicInteger(0);

    @Override
    public Future<?> submit(Runnable runnable) {
        // 1 定义一个FutureTask,返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
        FutureTask<Void> futureTask = new FutureTask<>();
        // 构造一个线程去执行提交的任务
        new Thread(() -> {
            runnable.run();
            // 任务执行结束后,将null设置给FutureTask
            futureTask.finish(null);
        }, getNextName()).start();
        // 返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
        return futureTask;
    }



    @Override
    public Future<R> submit(Task<I, R> task, I input) {
        // 1 定义一个FutureTask,返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
        FutureTask<R> futureTask = new FutureTask<>();
        // 构造一个线程去执行提交的任务
        new Thread(()->{
            R r = task.get(input);
            // 执行结束后,将执行结果设置给FutureTask
            futureTask.finish(r);
        }, getNextName()).start();
        // 返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
        return futureTask;
    }

    private String getNextName() {

        return THREAD_PREFIX + "_" + nextCounter.getAndIncrement();
    }
}

在FutureServiceImpl的submit方法中,分别启动了新的线程运行任务,起到了异步的作用,在任务最终运行成功之后,会通知FutureTask任务已完成。

3 测试

3.1 无返回值测试

public static void main(String[] args) throws InterruptedException {
    FutureServiceImpl<Void, Void> service = new FutureServiceImpl<>();

    Future<?> future = service.submit(() -> {
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程执行结束");
    });
    Object o = future.get();
    // 一旦获取到结果,就可以拿到结果干别的了,这里就简单判断一下
    // 测试的无返回值,所以输出为true
    System.out.println(o == null);
}

3.2 测试有返回值

    public static void main(String[] args) throws InterruptedException {
        FutureServiceImpl<String,Integer> futureService = new FutureServiceImpl<>();

        Future<Integer> future = futureService.submit(input -> {
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 就返回输入字符传的长度
            return input.length();
        },"HelloWorld");
        // 获取结果,获取不到就会等待
        Integer o = future.get();
        // 一旦获取到结果,就可以拿到结果干别的了,这里就简单输出一下:10
        System.out.println(o);
    }

通过测试,主线程就可以先干自己的事情,等到想获取结果的时候,通过Futute去获取结果即可,获取不到结果(说明任务还未执行完成),就会进入阻塞等待结果。

Future模式设计已讲解完毕,虽然我们提交任务时不会进入任何阻塞,但是当调用者需要获取结果的时候,还是有可能陷入阻塞直到任务完成

4 增强FutureService使其支持回调:

使用任务完成时回调的机制可以让调用者不再进行显式地通过get的方式获得数据而导致进入阻塞,可在提交任务的时候将回调接口一并注入,在这里对FutureService接口稍作修改:

  1. 先定义一个回调接口
@FunctionalInterface
public interface CallBack<T> {

    void call(T t);

    /**
     * 提供一个默认实现
     * @param <T>
     */
    static class DefaultCallBack<T> implements CallBack<T> {

        @Override
        public void call(T t) {
            // do nothing
        }
    }
}

也可以使用java8提供的Consumer接口

  1. futureService接口改造:,增加了一个Callback参数,主要用来接受并处理任务的计算结果,当提交的任务执行完成之后,会将结果传递给Callback接口进行进一步的执行,这样在提交任务之后不再会因为通过get方法获得结果而陷入阻塞。
 Future<R> submit(Task<I,R> task, I input,CallBack<R> callBack);

实现类修改:

@Override
public Future<R> submit(Task<I, R> task, I input,CallBack<R> callBack) {
    // 1 定义一个FutureTask,返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
    FutureTask<R> futureTask = new FutureTask<>();
    // 构造一个线程去执行提交的任务
    new Thread(()->{
        R r = task.get(input);
        // 执行结束后,将执行结果设置给FutureTask
        futureTask.finish(r);
        // 完成之后,主动调用使用定义的回调函数,避免阻塞
        callBack.call(r);
    }, getNextName()).start();
    // 返回给用户(FutureTask就是一个凭据,用户可以通过FutureTask来获取线程执行的结果)
    return futureTask;
}

测试:

public static void main(String[] args) throws InterruptedException {
   FutureServiceImpl<String,Integer> futureService = new FutureServiceImpl<>();

   Future<Integer> future = futureService.submit(input -> {
       try {
           TimeUnit.SECONDS.sleep(10);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
       // 就返回输入字符传的长度
       return input.length();
   },"HelloWorld",integer -> System.out.println(integer));

}

这样主线程在提交任务的时候顺便提交了一个任务完成时候的回调,这样当任务执行结束后,便会自动调用回调,不需要主线程等待任务执行完成(阻塞),自己去主动调用这部分逻辑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值