探究netty的观察者设计模式
javadoc笔记点
观察者的核心思想就是,在适当的时机回调观察者的指定动作函数
我们知道,在使用netty创建channel时,一般都是把这个channel设置成非阻塞的模式,这意味着什么呢? 意味着所有io操作一经调用,即刻返回
这让netty对io的吞吐量有了飞跃性的提升,但是异步编程相对于传统的串行化的编程模式来说,控制起来可太麻烦了
jdk提供了原生的Futrue接口,意为在未来任务,其实就是把任务封装起来交给新的线程执行,在这个线程执行任务的期间,我们的主线程可以腾出时间去做别的事情
下面的netty给出的实例代码,我们可以看到,任务线程有返回一个Futrue对象,这个对象中封装着任务执行的情况
* * void showSearch(final String target)
* * throws InterruptedException {
* * Future<String> future
* * = executor.submit(new Callable<String>() {
* * public String call() {
* * return searcher.search(target);
* * }});
* * displayOtherThings(); // do other things while searching
* * try {
* * displayText(future.get()); // use future
* * } catch (ExecutionException ex) { cleanup(); return; }
* * }
*
虽然jdk原生Futrue可以实现异步提交任务,并且返回了任务执行信息的Futrue,但是有一个致命的缺点,从futrue获取任务执行情况方法,是阻塞的,这是不被允许的,因为在netty中,一条channel可能关系着上千的客户端的链接,其中一个客户端的阻塞导致几千的客户端不可用是不被允许的,netty的Future设计成,继承jdk原生的future,而且进行扩展如下
// todo 这个接口继承了 java并发包总的Futrue , 并在其基础上增加了很多方法
// todo Future 表示对未来任务的封装
public interface Future<V> extends java.util.concurrent.Future<V> {
// todo 判断IO是否成功返回
boolean isSuccess();
// todo 判断是否是 cancel()方法取消
boolean isCancellable();
// todo 返回IO 操作失败的原因
Throwable cause();
/**
* todo 使用了观察者设计模式, 给这个future添加监听器, 一旦Future 完成, listenner 立即被通知
*/
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
// todo 添加多个listenner
Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
// todo 移除多个 listenner
Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
// todo sync(同步) 等待着 future 的完成, 并且,一旦future失败了,就会抛出 future 失败的原因
// todo bind()是个异步操作,我们需要同步等待他执行成功
Future<V> sync() throws InterruptedException;
// todo 不会被中断的 sync等待
Future<V> syncUninterruptibly();
// todo 等待
Future<V> await() throws InterruptedException;
Future<V> awaitUninterruptibly();
// todo 无阻塞的返回Future对象, 如果没有,返回null
// todo 有时 future成功执行后返回值为null, 这是null就是成功的标识, 如 Runable就没有返回值, 因此文档建议还要 通过isDone() 判断一下真的完成了吗
V getNow();
@Override
boolean cancel(boolean mayInterruptIfRunning);
...
netty的观察者模式
最常用的关于异步执行的方法writeAndFlush()
就是典型的观察者的实现, 在netty中,当一个IO操作刚开始的时候,一个ChannelFutrue
对象就会创建出来,此时,这个futrue对象既不是成功的,也不是失败的,更不是被取消的,因为这个IO操作还没有结束
如果我们想在IO操作结束后立刻执行其他的操作时,netty推荐我们使用addListenner()
添加监听者的方法而不是使用await()阻塞式等待,使用监听者,我们就不用关系具体什么时候IO操作结束,只需要提供回调方法就可以,当IO操作结束后,方法会自动被回调
在netty中,一个IO操作是状态分为如下几种
* +---------------------------+
* | Completed successfully |
* +---------------------------+
* +----> isDone() = true |
* +--------- -----------------+ | | isSuccess() = true |
* | Uncompleted | | +===========================+
* +--------------------------+ | | Completed with failure |
* | isDone() = false | | +---------------------------+
* | isSuccess() = false |----+----> isDone() = true |
* | isCancelled() = false | | | cause() = non-null 非空|
* | cause() = null | | +===========================+
* +--------------------------+ | | Completed by cancellation |
* | +---------------------------+
* +----> isDone() = true |
* | isCancelled() = true |
* +---------------------------+
源码追踪
对writeAndFlush的使用
ChannelFuture channelFuture = ctx.writeAndFlush("from client : " + UUID.randomUUID());
channelFuture.addListener(future->{
if(future.isSuccess()){
todo
}else{
todo
}
});
注意点: 我们使用writeAndFlush()
程序立即返回,随后我们使用返回的对象添加监听者,添加回调,这个时writeAndFlush()
有可能已经完成了,也有可能没有完成,这是不确定的事
首先我们知道,writeAndFlush()
是出站的动作,属于channelOutboundHandler
,而且他是从pipeline的尾部开始传播的,源码如下:
@Override
public final ChannelFuture writeAndFlush(Object msg) {
return tail.writeAndFlush(msg);
}
尾节点数据AbstractChannelHandlerContext
类, 继续跟进查看源码如下:
@Override
public final ChannelFuture writeAndFlush(Object msg) {
return tail.writeAndFlush(msg);
}
@Override
public ChannelPromise newPromise() {
return new DefaultChannelPromise(channel(), executor());
}
悄无声息的做了一个很重要的事情,创建了Promise
,这个DefaultChannelPromise
就是被观察者,过一会由它完成方法的回调
继续跟进writeAndFlush()
,源码如下, 我们可以看到promise
被返回了, DefaultChannelPromise
是ChannelPromise
的实现类,而ChannelPromise
又继承了ChannelFuture
,这也是为什么明明每次使用writeAndFlush()
返回的都是ChannelFuture
而我们这里却返回了DafaultChannelPromise
// todo 调用本类的 write(msg, true, promise)
@Override
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
if (msg == null) {
throw new NullPointerException("msg");
}
if (isNotValidPromise(promise, true)) {
ReferenceCountUtil.release(msg);
return promise;
}
write(msg, true, promise);
return promise;
在去目标地之前,先看一下addListenner()
干了什么,我们进入到DefaultChannelPromise
源码如下:
@Override
public ChannelPromise addListener(GenericFutureListener<? extends Future<? super Void>> listener) {
super.addListener(listener);
return this;
}
随机进入它的父类 DefaultChannelPromise中
@Override
public Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener) {
checkNotNull(listener, "listener");
synchronized (this) {
addListener0(listener);
}
if (isDone()) {
notifyListeners();
}
return this;
}
这个函数分两步进行
第一步: 为什么添加监听事件的方法需要同步?
在这种多线程并发执行的情况下,这个 addListener0(listener);
任意一个线程都能使用,存在同步添加的情况 这个动作不像将channel和EventLoop做的唯一绑定一样,没有任何必须使用inEventloop()
去判断在哪个线程中,直接使用同步
接着进入 addListener0(listener)
private void addListener0(GenericFutureListener<? extends Future<? super V>> listener) {
if (listeners == null) {
listeners = listener; // todo 第一次添加直接在这里赋值
} else if (listeners instanceof DefaultFutureListeners) {
// todo 第三次添加调用这里
((DefaultFutureListeners) listeners).add(listener);
} else {
// todo 第二次添加来这里复制, 由这个 DefaultFutureListeners 存放观察者
listeners = new DefaultFutureListeners((GenericFutureListener<?>) listeners, listener);
}
}
第二步: 为什么接着判断isDone()
writeAndFlush()
是异步执行的,而且在我们添加监听者的操作之前已经开始执行了,所以在添加完监听者之后,立即验证一把,有没有成功
思考一波:
回顾writeAndFlush()
的调用顺序,从tail开始传播两波事件,第一波write,紧接着第二波flush,一直传播到header,进入unsafe类中,由他完成把据写入jdk原生ByteBuffer
的操作, 所以按理说,我们添加是listenner的回调就是在header的unsafe中完成的,这是我们的目标地
任何方法的回调都是提前设计好了的,就像pipeline中的handler中的方法的回调,就是通过遍历pipeline内部的链表实现的,这里的通知观察者,其实也是调用观察者的方法,而且他使用的一定是观察的父类及以上的引用实现的方法回调
回到我们的writeAndFlush()
这个方法,在第二波事务传递完成,将数据真正写入jdk原生的ByteBuffer
之前,只有进行的所有回调都是设置失败的状态,直到把数据安全发出后才可能是 回调成功的操作
此外,想要进行回调的操作,就得有被观察的对象的引用,所以一会我就回看到,Promise
一路被传递下去
我们进入的unsafe的write()
就可以看到与回调相关的操作safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
,源码如下
@Override
public final void write(Object msg, ChannelPromise promise) {
assertEventLoop();
ChannelOutboundBuffer outboundBuffer = this.outboundBuffer;
if (outboundBuffer == null) { // todo 缓存 写进来的 buffer
safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
ReferenceCountUtil.release(msg);
return;
}
我们继续跟进本类方法safeSetFailure(promise, WRITE_CLOSED_CHANNEL_EXCEPTION);
, 源码如下:
protected final void safeSetFailure(ChannelPromise promise, Throwable cause) {
if (!(promise instanceof VoidChannelPromise) && !promise.tryFailure(cause)) {
logger.warn("Failed to mark a promise as failure because it's done already: {}", promise, cause);
}
}
其中重要的方法,就是回调 被观察者的 tryFailure(cause)
, 这个被观察者的类型是ChannelPromise
, 我们去看它的实现,源码如下
@Override
public boolean tryFailure(Throwable cause) {
if (setFailure0(cause)) {
notifyListeners();
return true;
}
return false;
}
调用本类方法notifyListeners()
继续跟进本类方法notifyListenersNow();
接着跟进本类方法 notifyListener0(this, (GenericFutureListener<?>) listeners);
继续 l.operationComplete(future);
终于看到了调用了监听者的完成操作,实际上就是回调用户的方法,虽然是完成的,但是失败了
下面我们去flush()
中去查看通知成功的回调过程, 方法的调用顺序如下
flush();
flush0();
doWrite(outboundBuffer);
在doWrite()方法中,就会使用自旋的方式往尝试把数据写出去, 数据被写出去后,有一个标识 done=true, 证明是成功写出了, 紧接着就是把当前的盛放ByteBuf的entry从链表上移除,源码出下
if (done) {
// todo 跟进去
in.remove();
} else {
我们继续跟进remove()
, 终于我们找到了成功回调的标志,在remove()
的底端safeSuccess(promise);, 下一步就是用回调用户添加的监听者操作完成了,并且完成的状态是Success
成功的
public boolean remove() {
// todo 获取当前的 Entry
Entry e = flushedEntry;
if (e == null) {
clearNioBuffers();
return false;
}
Object msg = e.msg;
ChannelPromise promise = e.promise;
int size = e.pendingSize;
// todo 将当前的Entry进行移除
removeEntry(e);
if (!e.cancelled) {
// only release message, notify and decrement if it was not canceled before.
ReferenceCountUtil.safeRelease(msg);
safeSuccess(promise);
decrementPendingOutboundBytes(size, false, true);
}
探究netty的观察者设计模式的更多相关文章
- (java)从零开始之--观察者设计模式Observer
观察者设计模式:时当一个对象发生指定的动作时,要通过另外的对象做出相应的处理. 步骤: 1. A对象发生指定的动作是,要通知B,C,D...对象做出相应的处理,这时候应该把B,C,D...对象针对A对 ...
- 《Head First设计模式》批注系列(一)——观察者设计模式
最近在读<Head First设计模式>一书,此系列会引用源书内容,但文章内容会更加直接,以及加入一些自己的理解. 观察者模式(有时又被称为模型-视图(View)模式.源-收听者(List ...
- Unity 3D观察者设计模式-C#委托和事件的运用
C#观察者设计模式 本文提供全流程,中文翻译. Chinar 坚持将简单的生活方式,带给世人!(拥有更好的阅读体验 -- 高分辨率用户请根据需求调整网页缩放比例) Chinar -- 心分享.心创新! ...
- IOS设计模式第七篇之观察者设计模式
版权声明:原创作品,谢绝转载!否则将追究法律责任. 观察者设计模式 在观察者设计模式里面,一个对象通知其他的对象一些状态的改变.涉及这些对象不需要知道另一个对象---因此鼓励解耦设计模式.这个设计模式 ...
- javaEE之--------统计站点在线人数,安全登录等(观察者设计模式)
整体介绍下: 监听器:监听器-就是一个实现待定接口的普通Java程序,此程序专门用于监听别一个类的方法调用.都是使用观察者设计模式. 小弟刚接触这个,做了些简单的介绍.大神请绕道,技术仅仅是一点点, ...
- Java常用类库——观察者设计模式
观察者设计模式 现在很多的购房者都在关注着房子的价格变化,每当房子价格变化的时候,所有的购房者都可以观察得到.实际上以上的购房者都属于观察者,他们都关注着房子的价格. 如果要想实现观察者模式,则必须依 ...
- JS观察者设计模式:实现iframe之间快捷通信
观察者设计模式又称订阅发布模式,在JS中我们习惯叫做广播模式,当多个对象监听一个通道时,只要发布者向该通道发布命令,订阅者都可以收到该命令,然后执行响应的逻辑.今天我们要实现的就是通过观察者设计模式, ...
- Java事件监听机制与观察者设计模式
一. Java事件监听机制 1. 事件监听三要素: 事件源,事件对象,事件监听器 2. 三要素之间的关系:事件源注册事件监听器后,当事件源上发生某个动作时,事件源就会调用事件监听的一个方法,并将事件对 ...
- 观察者设计模式(C#委托和事件的使用)
观察者设计模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新.在现实生活中的可见观察者模式,例如,微信中的订阅号,订阅博客和QQ微博中关注好友 ...
随机推荐
- mac下 编译php的 openssl
编译openssl.so tar zxvf php-7.2.8.tar.gz# 进入PHP的openssl扩展模块目录cd php-7.2.8/ext/openssl/brew install ope ...
- 平衡二叉树 JAVA实现 亲测可用
平衡二叉树的JAVA实现 亲测可用 包括LL LR RL RR四种情况的旋转算法 以及添加删除树结点之后对平衡二叉树的维护算法 都已经实现并测试过 没有问题. 代码地址可以直接上我的GIT clone ...
- Tomcat常见问题[内存溢出,虚拟目录配置等](一)
一.Tomcat内存溢出的解决方法 内存溢出一般有如下三种常见的原因: OutOfMemoryError: Java heap space OutOfMemoryError: PermGen spac ...
- 基于Google Earth Engine的全国地表温度反演
国内研究landsat8温度反演的人员很多,但是现有算法一般都是一景为例子,进行开展. 这有一个局限性,当研究的尺度很大时,就需要比较大的运算量了,例如全省温度,全国温度,全球温度,当然大家可能会说, ...
- python爬虫之快速对js内容进行破解
python爬虫之快速对js内容进行破解 今天介绍下数据被js加密后的破解方法.距离上次发文已经过去半个多月了,我写文章的主要目的是把从其它地方学到的东西做个记录顺便分享给大家,我承认自己是个懒猪.不 ...
- django基础知识之上传图片:
上传图片 当Django在处理文件上传的时候,文件数据被保存在request.FILES FILES中的每个键为<input type="file" name="& ...
- web前端css(二)
一. 标准文档流 标准文档流中会有一些现象: 空白折叠 和 高低不齐边底对齐的现象 标准文档流等级森严, 标签分为两种等级: 行内元素 和 块级元素. 1. 行内元素 和 块级元素的区别: 行内元素 ...
- kuangbin专题 专题二 搜索进阶 Escape HDU - 3533
题目链接:https://vjudge.net/problem/HDU-3533 题目分析: 1.人不能经过碉堡; 2.敌军碉堡可能建到我军基地 3.子弹碰到碉堡就没了,说明子弹会被别的城堡给拦截下来 ...
- kuangbin专题 专题二 搜索进阶 哈密顿绕行世界问题 HDU - 2181
题目链接:https://vjudge.net/problem/HDU-2181 题意:一个规则的实心十二面体,它的 20个顶点标出世界著名的20个城市,你从一个城市出发经过每个城市刚好一次后回到出发 ...
- PCB 板边倒圆角的实现方法(基本算法一)
PCB外形是直角时外形时,通常工程制作时,外是直角或尖角的地方倒圆角,主要是为了防止板边容易划伤板且容易扎伤人 所以当客户没有特殊要求时,PCB外形是直角时一般会默认倒角0.5mm圆角(如下图所示) ...