文档章节

Future研究

xilidou
 xilidou
发布于 2017/05/07 23:53
字数 1482
阅读 186
收藏 1

Future是什么?

最近写了一些关于netty的相关代码,发现类似netty 的这种异步框架大量的使用一个Future的类。利用这个Futuer类可以实现,代码的异步调用,程序调用耗时的网络或者IO相关的方法的时候,首先获得一个Future的代理类,同时线程并不会被阻塞。继续执行之后的逻辑,直到真正要使用远程调用返回的结果的时候,才需要调用Futuer的get()方法。这样可以提高代码的执行效率。
于是就花了一点时间研究Futuer是如何实现的。调用方式如何知道,结果什么时候返回的呢?如果使用一个线程去轮询flag 标记,那么就很难及时的感知对象的改变,同时还很难降低开销。所以我们需要了解java的等待通知机制。利用这个机制来构建一个节能环保的Future。

等待通知机制

一个线程修改了一个对象的值,另一个线程感知到了变化,然后进行相应的操作。一个线程是生产者,另一个线程是消费者。这种模式做到了解耦,隔离了“做什么”和“做什么”。如果要实现这个功能,我们可以利用java内对象内置的等待通知机制来实现。
我们知道'java.lang.Object'有以下方法

方法名称 描述
notify() 随机选择通知一个在对象上等待的的线程,解除其阻塞状态。
notfiyAll() 解除所有那些在该对象上调用wait方法的线程的阻塞状态
wait() 导致线程进入等待状态。
wait(long) 同上,同时设置一个超时时间,线程等待一段时间。
wait(long,int) 同上,且为超时时间设置一个单位。

ps:敲黑板,面试中面试官可能会问,你了解'Object'的哪些方法?如果只答出 toString()的话。估计得出门右转慢走不送了。

所谓等待通知机制,就是某个线程A调用了对象 O 的wait()方法,另一个线程B调用对象 O 的 notify() 或者 notifyAll() 方法。 线程 A 接收到线程 B 的通知,从wait状态中返回,继续执行后续操作。两个线程通过对象 O 来进行通信。
我们看damo:


public class waitAndNotify {

    private static Object object = new Object();
    private static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {

        Thread a = new Thread(new waitThread(),"wait");
        a.start();

        TimeUnit.SECONDS.sleep(5);

        Thread b = new Thread(new notifyThread(),"notify");
        b.start();

    }

    static class waitThread implements Runnable{
        @Override
        public void run() {
            synchronized (object){
                while (flag){
                    try {
                        System.out.println(Thread.currentThread() + "flag is true wait @" +
                                new SimpleDateFormat("HH:mm:ss").format(new Date()));
                        object.wait();
                    }catch (InterruptedException e){
                    }
                }

                System.out.println(Thread.currentThread() + "flag is false go on @"+
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
            }

        }
    }

    static class notifyThread implements Runnable{

        @Override
        public void run() {
            synchronized (object){
                System.out.println(Thread.currentThread() + "lock the thread and change flag" +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
                object.notify();
                flag = false;
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            synchronized (object){
                System.out.println(Thread.currentThread() + "lock the thread again@" +
                        new SimpleDateFormat("HH:mm:ss").format(new Date()));
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

程序的输出:

Thread[wait,5,main]flag is true wait @22:46:52
Thread[notify,5,main]lock the thread and change flag22:46:57
Thread[notify,5,main]lock the thread again@22:47:02
Thread[wait,5,main]flag is false go on @22:47:07

  1. wait()notify()以及notifyAll() 需要在对象被加锁以后会使用。
  2. 调用notify()notifyAll() 后,对象并不是立即就从wait()返回。而是需要对象的锁释放以后,等待线程才会从wait()中返回。

等待通知经典范式

通过以上的代码我们可以把等待通知模式进行抽象。
wait线程:
1. 获取对象的锁。
2. 条件不满足,调用对象wait()方法。
3. 等待另外线程通知,如果满足条件,继续余下操作执行。
伪码如下:

lock(object){
    while(condition){
        object.wait();
    }
    doOthers();
}

notify线程:
1. 获取对象的锁。
2. 修改条件。
3. 调用对象的notify()或者notifyAll()方法通知等待的线程。
4. 释放锁.
伪码如下:

lock(object){
    change(condition);
    objcet.notify();
}

Future的实现原理:

了解了java的等待通知机制,我们来看看如何利用这个机制实现一个简单的Future。
首先我们定义一个Future的接口:

public interface IData {

    String getResult();

}

假设我们有一个很耗时的远程方法:

public class RealData implements IData {

    private String result;
    RealData(String str){
        StringBuilder sb = new StringBuilder();
        //假设一个相当耗时的远程方法
        for (int i = 0; i < 20; i++) {
            sb.append("i").append(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        result = sb.append(str).toString();
    }

    @Override
    public String getResult() {
        return result;
    }
}

同时还要有一个实现了IDataRealData包装类:

public class FutureData implements IData {

    private RealData realData;
    private volatile boolean isReal = false;

    @Override
    public synchronized String getResult() {

        while (!isReal){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return realData.getResult();
    }

    public synchronized void setReault(RealData realData){
        if(isReal){
            return;
        }

        this.realData = realData;
        isReal = true;
        notifyAll();

    }
}

可以看出来我们的这个包装类就是一个相当标准的等待通知机制的类。

再看看我们Service类,在Service中的getData方法被调用的时候,程序只接返回了一个FutureData的代理类,同时起了一个新的线程去执行真正耗时的RealData

public class Service {

    public IData getData(final String str){

        final FutureData futureData = new FutureData();
        new Thread(new Runnable() {
            @Override
            public void run() {
                RealData realData = new RealData(str);
                futureData.setReault(realData);
            }
        }).start();
        return futureData;
    }

}

最后看看是如何使用的:

public class Clinet {

    public static void main(String[] args) {

        Service service = new Service();

        IData data = service.getData("test");

        System.out.println("a");
        System.out.println("b");

        System.out.println("result is " + data.getResult());

    }

}

执行的结果是:

a
b
result is i0i1i2i3i4i5i6i7i8i9i10i11i12i13i14i15i16i17i18i19test

可见程序并没有因为调用耗时的方法阻塞,先打印了a和b,在程序调用getReslut()才打印出真正的结果。

总结:

通过以上的讲解,我们总结一下Futuer,首先使用Futuer可以实现异步调用,实现Futuer我们使用了java的等待通知机制。这个时候们回过头再来看netty的Futuer就很简单了。

参考

《java并发编程的艺术》
漫谈并发编程:Future模型(Java、Clojure、Scala多语言角度分析)

© 著作权归作者所有

上一篇: Netty-Apns接入
下一篇: Hystrix入门研究
xilidou
粉丝 1
博文 10
码字总数 13682
作品 0
东城
程序员
私信 提问
黑客销售合法的代码签名证书,以逃避恶意软件检测

近日,安全研究员发现,黑客正在使用代码签名证书,企图绕过安全设备并感染受害者。 Recorded Future 的 Insikt Group 的最新研究发现,黑客和恶意行为份子从发证当局获取合法证书以签署恶意...

达尔文
2018/02/27
1K
4
Flash 在 EK 被利用漏洞 Top 10 中占 8 个位置

据外媒报道,日前,威胁情报研究公司Recorded Future整理出了一份被Exploit Kits(以下简称EK)利用最多的应用名单。研 究时间从2015年1月1日至9月30日,研究对象为当下在各种类型网络攻击中...

oschina
2015/11/11
1K
7
Windows技术文章汇集

缓冲区溢出研究 http://www.abysssec.com/blog/2010/05/08/past-present-future-of-windows-exploitation/ http://www.corelan.be/index.php/2009/07/19/exploit-writing-tutorial-part-1-st......

长平狐
2012/08/13
54
0
Callable和Future学习笔记

一。 1>Callable和Future接口介绍 Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可以被创建为线程的来执行的类。 2>Callable和Runnable比较 1. Callable规定...

tongqu
2016/02/17
25
0
Java中ExecutorService和CompletionService区别

分享一篇关于Java中ExecutorService和CompletionService区别,有需要的朋友可以参考一下。 我们现在在Java中使用多线程通常不会直接用Thread对象了,而是会用到java.util.concurrent包下的Exe...

孟飞阳
2016/06/27
48
0

没有更多内容

加载失败,请刷新页面

加载更多

Angular 英雄编辑器

应用程序现在有了基本的标题。 接下来你要创建一个新的组件来显示英雄信息并且把这个组件放到应用程序的外壳里去。 创建英雄组件 使用 Angular CLI 创建一个名为 heroes 的新组件。 ng gener...

honeymoose
今天
5
0
Kernel DMA

为什么会有DMA(直接内存访问)?我们知道通常情况下,内存数据跟外设之间的通信是通过cpu来传递的。cpu运行io指令将数据从内存拷贝到外设的io端口,或者从外设的io端口拷贝到内存。由于外设...

yepanl
今天
6
0
hive

一、hive的定义: Hive是一个SQL解析引擎,将SQL语句转译成MR Job,然后再在Hadoop平台上运行,达到快速开发的目的 Hive中的表是纯逻辑表,就只是表的定义,即表的元数据。本质就是Hadoop的目...

霉男纸
今天
3
0
二、Spring Cloud—Eureka(Greenwich.SR1)

注:本系列文章所用工具及版本如下:开发工具(IDEA 2018.3.5),Spring Boot(2.1.3.RELEASE),Spring Cloud(Greenwich.SR1),Maven(3.6.0),JDK(1.8) Eureka: Eureka是Netflix开发...

倪伟伟
昨天
13
0
eclipse常用插件

amaterasUML https://takezoe.github.io/amateras-update-site/ https://github.com/takezoe/amateras-modeler modelGoon https://www.cnblogs.com/aademeng/articles/6890266.html......

大头鬼_yc
昨天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部