前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >怎么用wait、notify巧妙的设计一个Future模式?

怎么用wait、notify巧妙的设计一个Future模式?

作者头像
烟雨星空
发布2020-06-16 16:03:18
4810
发布2020-06-16 16:03:18
举报
文章被收录于专栏:Java进阶指南Java进阶指南

我们知道多线程可以实现同时执行多个任务(只是看起来是同时,其实是CPU的时间片切换特别快我们没感觉而已)。

现在假设一个做饭的场景,你没有厨具也没有食材。你可以去网上买一个厨具,但是这段时间,你不需要闲着啊,可以同时去超市买食材。

设想这是两个线程,主线程去买食材,然后开启一个子线程去买厨具。但是,子线程是需要返回一个厨具的。如果用普通的线程,只有一个Run方法,而Run方法是没有返回值的,这个时候该怎么办呢?

我们就可以用JDK提供的Future模式。在主线程买完食材之后,可以主动去获取子线程的厨具。(本文认为读者了解Future,因此不对Future用法做过多介绍)

代码如下:

代码语言:javascript
复制
 1public class FutureCook {
 2    static class Chuju {
 3
 4    }
 5
 6    static class Shicai{
 7
 8    }
 9
10    public static void cook(Chuju chuju,Shicai shicai){
11        System.out.println("最后:烹饪中...");
12    }
13
14    public static void main(String[] args) throws InterruptedException, ExecutionException {
15        //第一步,网购厨具
16        Callable<Chuju> shopping = new Callable<Chuju>(){
17
18            @Override
19            public Chuju call() throws Exception {
20                System.out.println("第一步:下单");
21                System.out.println("第一步:等待送货");
22                Thread.sleep(5000); //模拟送货时间
23                System.out.println("第一步:快递送到");
24                return new Chuju();
25            }
26        };
27
28        FutureTask<Chuju> task = new FutureTask<Chuju>(shopping);
29        new Thread(task).start();
30
31        //第二步,购买食材
32        Thread.sleep(2000);
33        Shicai shicai = new Shicai();
34        System.out.println("第二步:食材到位");
35
36        //第三步,烹饪
37        if(!task.isDone()){ //是否厨具到位
38            System.out.println("第三步:厨具还没到,请等待,也可以取消");
39            //①
40//            task.cancel(true);
41//            System.out.println("已取消");
42//            return;
43        }
44
45        //尝试获取结果,如果获取不到,就会进入等待状态
46        // 即main线程等待子线程执行结束才能继续往下执行
47        Chuju chuju = task.get();
48        System.out.println("第三步:厨具到位,可以烹饪了");
49
50        cook(chuju,shicai);
51
52    }
53}

返回结果:

代码语言:javascript
复制
1第一步:下单
2第一步:等待送货
3第二步:食材到位
4第三步:厨具还没到,请等待,也可以取消
5第一步:快递送到
6第三步:厨具到位,可以烹饪了
7最后:烹饪中...

以上代码表示,子线程购买厨具消耗的时间比较长(假定5秒),而主线程购买食材比较快(2秒),所以我在第三步烹饪之前,先去判断一下买厨具的线程是否执行完毕。此处肯定返回false,然后主线程可以选择继续等待,也可以选择取消。(把①注释打开即可测试取消)

我们可以看到,利用Future模式,可以把原本同步执行的任务改为异步执行,可以充分利用CPU资源,提高效率。

现在,我用wait、notify的方式来实现和以上Future模式一模一样的效果。

大概思想就是,创建一个FutureClient端去发起请求,通过FutureData先立即返回一个结果(此时相当于只返回一个请求成功的通知),然后再去开启一个线程异步地执行任务,获取真实数据RealData。此时,主线程可以继续执行其他任务,当需要数据的时候,就可以调用get方法拿到真实数据。

1)定义一个数据接口,包含获取数据的get方法,判断任务是否执行完毕的isDone方法,和取消任务的cancel方法。

代码语言:javascript
复制
1public interface Data<T> {
2
3    T get();
4
5    boolean isDone();
6
7    boolean cancel();
8}

2)定义真实数据的类,实现Data接口,用来执行实际的任务和返回真实数据。

代码语言:javascript
复制
 1public class RealData<T> implements Data<T>{
 2
 3    private T result ;
 4
 5    public RealData (){
 6        this.prepare();
 7    }
 8
 9    private void prepare() {
10        //准备数据阶段,只有准备完成之后才可以继续往下走
11        try {
12            System.out.println("第一步:下单");
13            System.out.println("第一步:等待送货");
14            Thread.sleep(5000);
15            System.out.println("第一步:快递送到");
16        } catch (InterruptedException e) {
17            System.out.println("被中断:"+e);
18            //重新设置中断状态
19            Thread.currentThread().interrupt();
20        }
21        Main.Chuju chuju = new Main.Chuju();
22        result = (T)chuju;
23    }
24
25    @Override
26    public T get() {
27        return result;
28    }
29
30    @Override
31    public boolean isDone() {
32        return false;
33    }
34
35    @Override
36    public boolean cancel() {
37        return true;
38    }
39
40}

prepare方法用来准备数据,其实就是执行的实际任务。get方法用来返回任务的执行结果。

3)定义一个代理类FutureData用于给请求端FutureClient暂时返回一个假数据。等真实数据拿到之后,再装载真实数据。

代码语言:javascript
复制
 1public class FutureData<T> implements Data<T>{
 2
 3    private RealData<T> realData ;
 4
 5    private boolean isReady = false;
 6
 7    private Thread runningThread;
 8
 9    public synchronized void setRealData(RealData realData) {
10        //如果已经装载完毕了,就直接返回
11        if(isReady){
12            return;
13        }
14        //如果没装载,进行装载真实对象
15        this.realData = realData;
16        isReady = true;
17        //进行通知
18        notify();
19    }
20
21    @Override
22    public synchronized T get() {
23        //如果没装载好 程序就一直处于阻塞状态
24        while(!isReady){
25            try {
26                wait();
27            } catch (InterruptedException e) {
28                e.printStackTrace();
29            }
30        }
31        //装载好直接获取数据即可
32        return realData.get();
33    }
34
35
36    public boolean isDone() {
37        return isReady;
38    }
39
40    @Override
41    public boolean cancel() {
42        if(isReady){
43            return false;
44        }
45        runningThread.interrupt();
46        return true;
47    }
48
49    public void setRunningThread(){
50        runningThread = Thread.currentThread();
51    }
52}

如果get方法被调用,就会去判断数据是否已经被加载好(即判断isReady的值),如果没有的话就调用wait方法进入等待。

setRealData用于去加载真实的数据,加载完毕之后就把isReady设置为true,然后调用notify方法通知正在等待的线程。此时,get方法收到通知就继续执行,然后返回真实数据realData.get().

另外也简单的实现了一个取消任务的方法cancel,去中断正在执行子任务的线程。

4)FutureClient客户端用于发起请求,异步执行任务。

代码语言:javascript
复制
 1public class FutureClient {
 2
 3    public Data call(){
 4        //创建一个代理对象FutureData,先返回给客户端(无论是否有值)
 5        final FutureData futureData = new FutureData();
 6        //启动一个新的线程,去异步加载真实的对象
 7        new Thread(new Runnable() {
 8            @Override
 9            public void run() {
10                //此处注意需要记录一下异步加载真实数据的线程,以便后续可以取消任务。
11                futureData.setRunningThread();
12                RealData realData = new RealData();
13                //等真实数据处理完毕之后,把结果赋值给代理对象
14                futureData.setRealData(realData);
15            }
16        }).start();
17
18        return futureData;
19    }
20
21}

5)测试

代码语言:javascript
复制
 1public class Main {
 2
 3    static class Chuju{
 4
 5    }
 6
 7    static class Shicai{
 8
 9    }
10
11    public static void main(String[] args) throws InterruptedException {
12
13        FutureClient fc = new FutureClient();
14        Data data = fc.call();
15
16        Thread.sleep(2000);
17        Shicai shicai = new Shicai();
18        System.out.println("第二步:食材到位");
19
20        if(!data.isDone()){
21            System.out.println("第三步:厨具还没到,请等待或者取消");
22            //②
23//            data.cancel();
24//            System.out.println("已取消");
25//            return;
26        }
27
28        //真正需要数据的时候,再去获取
29        Chuju chuju = (Chuju)data.get();
30        System.out.println("第三步:厨具到位,可以烹饪了");
31
32        cook(chuju,shicai);
33
34    }
35
36    public static void cook (Chuju chuju, Shicai shicai){
37        System.out.println("最后:烹饪中...");
38    }
39}

执行结果和用JDK提供的Future模式是一模一样的。我们也可以把②出的代码打开,测试任务取消的结果。

代码语言:javascript
复制
1第一步:下单
2第一步:等待送货
3第二步:食材到位
4第三步:厨具还没到,请等待或者取消
5已取消
6被中断:java.lang.InterruptedException: sleep interrupted

执行取消之后,执行RealData的子线程就会被中断,然后结束任务。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 烟雨星空 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档