惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

TaoSecurity Blog
TaoSecurity Blog
L
LINUX DO - 最新话题
Help Net Security
Help Net Security
N
News | PayPal Newsroom
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
The Last Watchdog
The Last Watchdog
S
Security @ Cisco Blogs
W
WeLiveSecurity
C
CXSECURITY Database RSS Feed - CXSecurity.com
Webroot Blog
Webroot Blog
T
Troy Hunt's Blog
V
Vulnerabilities – Threatpost
Google Online Security Blog
Google Online Security Blog
N
News and Events Feed by Topic
T
Threat Research - Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tor Project blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
D
Darknet – Hacking Tools, Hacker News & Cyber Security
PCI Perspectives
PCI Perspectives
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
S
SegmentFault 最新的问题
J
Java Code Geeks
P
Privacy & Cybersecurity Law Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园 - 【当耐特】
博客园_首页
H
Hacker News: Front Page
T
Threatpost
Jina AI
Jina AI
博客园 - Franky
月光博客
月光博客
L
LINUX DO - 热门话题
The Cloudflare Blog
H
Heimdal Security Blog
博客园 - 司徒正美
酷 壳 – CoolShell
酷 壳 – CoolShell
Cloudbric
Cloudbric
雷峰网
雷峰网
Hugging Face - Blog
Hugging Face - Blog
S
Secure Thoughts
T
Tenable Blog
I
Intezer
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

犀利豆的博客

《SRE google 运维解密》读书笔记 (六) 《SRE google 运维解密》读书笔记 (五) 《SRE google 运维解密》读书笔记 (四) 《SRE google 运维解密》读书笔记 (三) 《SRE google 运维解密》读书笔记 (二) 《SRE google 运维解密》读书笔记 (一) 2021 总结 终于有一个 Java 可以用的微信机器人了 Vertx入门到实战—实现钉钉机器人内网穿透代理 钉钉机器人回调内网穿透代理--使用篇 周末补习(一)trie 树 那些有趣的代码(三)--勤俭持家的 ArrayList 那些有趣的代码(二)--偏不听父母话的 Tomcat 类加载器 那些有趣的代码(一)--有点萌的 Tomcat 的线程池 从需求第三定律说起--为什么知乎的回答质量下降了 如何利用 Spring Hibernate 高级特性设计实现一个权限系统 居然有人能忘记吃饭?写个微信机器人提醒他 我的2018年总结 从 LongAdder 中窥见并发组件的设计思路 徒手撸框架--实现 RPC 远程调用 我的写作工具链 Java 渲染 docx 文件,并生成 pdf 加水印 撸码的福音--变量名生成器的实现 Raft 协议学习笔记 dubbo 源码学习(一)开篇 Redis 命令的执行过程 Redis 中的事件驱动模型 Redis 数据库、键过期的实现 Redis 的基础数据结构(三)对象 Redis 的基础数据结构(二) 整数集合、跳跃表、压缩列表 Redis 的基础数据结构(一) 可变字符串、链表、字典 线程池 execute() 的工作逻辑 JAVA 中的 CAS 徒手撸框架--高并发环境下的请求合并 徒手撸框架--实现Aop 徒手撸框架--实现IoC 2017个人总结 最近遇到的几个问题集合 Redis RedLock 完美的分布式锁么? JAVA 8入门(二)流 JAVA 8入门(一)Lambda表达式 有道 Alfred Workflow 威力加强版 Kafka实现原理笔记 《交易系统:更新与跨越》读后笔记 Netty-Apns接入实现 Hystrix入门研究 Redis实现分布式锁
Future研究
Zhengxin Diao · 2017-10-24 · via 犀利豆的博客

最近写了一些关于netty的相关代码,发现类似netty 的这种异步框架大量的使用一个Future的类。利用这个future类可以实现,代码的异步调用,程序调用耗时的网络或者IO相关的方法的时候,首先获得一个Future的代理类,同时线程并不会被阻塞。继续执行之后的逻辑,直到真正要使用远程调用返回的结果的时候,才需要调用future的get()方法。这样可以提高代码的执行效率。
于是就花了一点时间研究future是如何实现的。调用方式如何知道,结果什么时候返回的呢?如果使用一个线程去轮询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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67

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. 等待另外线程通知,如果满足条件,继续余下操作执行。
    伪码如下:
1
2
3
4
5
6
lock(object){
while(condition){
object.wait();
}
doOthers();
}

notify线程:

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

Future的实现原理:

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

1
2
3
4
5
public interface IData {

String getResult();

}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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包装类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

}

最后看看是如何使用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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());

}

}

执行的结果是:

1
2
3
a
b
result is i0i1i2i3i4i5i6i7i8i9i10i11i12i13i14i15i16i17i18i19test

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

总结:

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

参考

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