前言

最近趁着跟老东家提离职之后、到新公司报道之前的这段空闲时期,着力研究了一番netty框架,对其有了一些浅薄的认识,后续的几篇文章会以netty为主,将近期所学记录一二,也争取能帮未对netty有过了解的园友对netty建立一个完整的认识。netty作为一个优秀的网络框架,值得为其花费一番时间。

netty的内容细究一下也有不少(虽然与Spring这种庞大的框架相比代码量少很多),本文作为netty系列的第一篇,决定先攀登一个高峰:讲一下netty的串行无锁化。这是netty的一个招牌特性,可以说理解了它,就掌握了netty的命门。开始正文之前,需要额外提醒一下,本文虽然是netty系列的第一篇,但是面向的对象是对netty有过一定了解的园友,如果是新人建议从第二篇【EventLoopGroup的初始化】开始看。

一、Talk is cheap,show me your code

先把netty的示例demo奉上,此处只要服务端构建的代码就可以了。

 public class NettyDemo1 {
// netty服务端的一般性写法
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new NettyServerHandler());
}
});
ChannelFuture channelFuture = bootstrap.bind(90).sync();
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}

从第4行到第18行,都是在进行初始化的属性赋值,第19行bind方法触发真正的串行无锁化处理逻辑。串行无锁化如何理解呢?望文生义即可,通过串行(即顺序执行),来达到即使没有锁也可以线程安全的效果。具体如何做到呢?且往下追踪bind方法。

二、源码追踪

1、initAndRegister方法

在AbstractBootstrap类中的doBind方法调用了下面的initAndRegister方法(该方法在netty中很重要,现在先记住混个眼熟),前两步虽然也很重要,但跟本文的主题关系不大,下面主要看第3步:

 final ChannelFuture initAndRegister() {
Channel channel = null;
try {
channel = channelFactory.newChannel();// 1、实例化NioServerSocketChannel
init(channel);// 2、初始化该channel
} catch (Throwable t) {
// ...省略异常处理
}
ChannelFuture regFuture = config().group().register(channel);// 3、注册channel
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
return regFuture;
}

第3步中,group()方法返回的是AbstractBootstrap中的group属性,该属性就是上面服务端demo中的boss变量。

2、boss.register(channel)方法

追踪进入MultithreadEventLoopGroup的register方法:

 public ChannelFuture register(Channel channel) {
return next().register(channel);
}

next方法即从EventLoopGroup的EventExecutor数组中轮询取一个EventExecutor实例,即一个NioEventLoop对象,然后再调用NioEventLoop的register方法。

3、NioEventLoop.register(channel)方法

跟踪到SingleThreadEventLoop的register方法,如下,此处的promise.channel()返回值即之前的NioServerSocketChannel,它的unsafe()方法返回NioMessageUnsafe对象,所以此处最终调用的是NioMessageUnsafe的register方法。

 public ChannelFuture register(final ChannelPromise promise) {
ObjectUtil.checkNotNull(promise, "promise");
promise.channel().unsafe().register(this, promise);
return promise;
}

4、NioMessageUnsafe.register方法

该方法位于AbstractChannel的内部类AbstractUnsafe中(AbstractUnsafe是NioMessageUnsafe的父类):

 public final void register(EventLoop eventLoop, final ChannelPromise promise) {
//... 省略异常校验
AbstractChannel.this.eventLoop = eventLoop; if (eventLoop.inEventLoop()) {// 串行无锁化的第一个判断
register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
//... 省略异常处理
}
}
}

在eventLoop的父类SingleThreadEventExecutor中有一个成员变量thread,第5行代码用来判断这个thread是不是Thread.currentThread(),如果不是,则进入esle逻辑,执行eventLoop.execute方法。

5、eventLoop.execute方法

该方法位于SingleThreadEventExecutor中,主要做的事情有三步:1)、inEventLoop()方法判断当前线程是不是eventLoop中记录的线程;2)、addTask将任务放入队列中;3)、startThread()判断是否启动新线程。每步对应的代码在下面已经标出,比较简单。

执行到execute方法时,有三种情况:1)、eventLoop中记录的线程为null,即刚完成实例化;2)、eventLoop中记录的线程不为null,但不是当前线程;3)、eventLoop中记录的线程不为null,是当前线程(正常不会出现这种情况)。如果是第一种thread=null,则往队列中添加完任务后会进入startThread方法,在startThread方法中判断state属性是不是未启动,如果是则创建一个新的线程并通过cas将state置为已启动;如果是第二种情况thread!=null,往队列中添加任务后也会进入startThread方法,但由于state属性已经是已启动了,所以不会创建新的线程。至此,task中都添加了一个任务,且thread也有值了。

task中的任务是合适被执行的?且往下看。

 public void execute(Runnable task) {
if (task == null) {
throw new NullPointerException("task");
} boolean inEventLoop = inEventLoop();
addTask(task); // 将task任务放入队列中
if (!inEventLoop) { // 串行无锁化的第二次判断
startThread(); // 如果需要,会启动一个线程 重要***
if (isShutdown()) {
boolean reject = false;
try {
if (removeTask(task)) {
reject = true;
}
} catch (UnsupportedOperationException e) {
}
if (reject) {
reject();
}
}
} if (!addTaskWakesUp && wakesUpForTask(task)) {
wakeup(inEventLoop);
}
}

6、doStartThread()方法

此方法位于SingleThreadEventExecutor类,executor即封装的线程池了,在run方法中完成了对thread的赋值,然后执行了当前类的run方法。

7、SingleThreadEventExecutor.run()方法

该方法的实现在NioEventLoop中,如下,多出对 runAllTasks()方法进行调用,就是在这个方法中完成的对队列中任务的执行,直接调用的task.run方法,即单线程串行消费队列。

 protected void run() {
for (;;) {
try {
try {
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
case SelectStrategy.CONTINUE:
continue;
case SelectStrategy.BUSY_WAIT:
case SelectStrategy.SELECT:
select(wakenUp.getAndSet(false));
if (wakenUp.get()) {
selector.wakeup();
}
// fall through
default:
}
} catch (IOException e) {
// ...异常处理省略
continue;
} cancelledKeys = 0;
needsToSelectAgain = false;
final int ioRatio = this.ioRatio;
if (ioRatio == 100) {
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
runAllTasks();
}
} else {
final long ioStartTime = System.nanoTime();
try {
processSelectedKeys();
} finally {
// Ensure we always run tasks.
final long ioTime = System.nanoTime() - ioStartTime;
runAllTasks(ioTime * (100 - ioRatio) / ioRatio);
}
}
} catch (Throwable t) {
handleLoopException(t);
}
// ...省略无关代码
}
}

三、小结

上面是过了一遍netty串行无锁化的流程,下面对整个流程做一下总结。

netty串行无锁化的实现,是借助了SingleThreadEventExecutor中的Thread thread、int state和Queue<Runnable> taskQueue这三个成员变量。state变量用于判断当前EventExecutor是否启动(未启动则通过线程池创建一个启动线程并赋值给thread),thread变量用来判断当前线程是不是启动线程(通过inEventLoop方法实现),taskQueue用于存放待启动线程串行执行的任务。在NioEventLoop的run方法中,每一次循环都遍历一遍taskQueue执行里面的任务。

可以知道,一个NioEventLoop对应一个串行执行的启动线程,Reactor主线程对应的是boss中的一个NioEventLoop,负责串行执行客户端连接事件,Reactor子线程是有客户端接入事件后由主线程启用的,对应worker中的一个NioEventLoop,负责串行执行客户端读写时间

至此,串行无锁化的内容就结束了,后面将从EventLoopGroup的初始化开始,一步步从零开始拆解netty的作用原理,敬请期待!

原创不易,若有问题,还请批评指正,感谢!

Netty源码学习系列之1-netty的串行无锁化的更多相关文章

  1. Netty源码学习系列之4-ServerBootstrap的bind方法

    前言 今天研究ServerBootstrap的bind方法,该方法可以说是netty的重中之重.核心中的核心.前两节的NioEventLoopGroup和ServerBootstrap的初始化就是为b ...

  2. Netty源码学习系列之1-NioEventLoopGroup的初始化

    前言 NioEventLoopGroup是netty对Reactor线程组这个抽象概念的具体实现,其内部维护了一个EventExecutor数组,而NioEventLoop就是EventExecuto ...

  3. Netty源码学习系列之5-NioEventLoop的run方法

    前言     NioEventLoop的run方法,是netty中最核心的方法,没有之一.在该方法中,完成了对已注册的channel上来自底层操作系统的socket事件的处理(在服务端时事件包括客户端 ...

  4. Netty源码学习系列之2-ServerBootstrap的初始化

    前言 根据前文我们知道,NioEventLoopGroup和NioEventLoop是netty对Reactor线程模型的实现,而本文要说的ServerBootstrap是对上面二者的整合与调用,是一 ...

  5. Netty源码学习(四)Netty服务器是如何启动的?

    本文会分析Netty服务器的启动过程,采用的范例代码是Netty编写的Echo Server. 0. 声明acceptor与worker 由于Netty采用的reactor模型,所以需要声明两组线程, ...

  6. Netty源码学习(一)Netty线程模型

    给你一台4路E7-4820V2(32核心64线程),512G内存的服务器,你该如何编程才能支持百万长连接? 最直接的想法是采用BIO的模式,为每个连接新建一个线程,在一一对应的线程中直接处理连接上的数 ...

  7. Netty 源码学习——EventLoop

    Netty 源码学习--EventLoop 在前面 Netty 源码学习--客户端流程分析中我们已经知道了一个 EventLoop 大概的流程,这一章我们来详细的看一看. NioEventLoopGr ...

  8. Netty 源码分析系列(二)Netty 架构设计

    前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...

  9. 【Netty源码学习】DefaultChannelPipeline(三)

    上一篇博客中[Netty源码学习]ChannelPipeline(二)我们介绍了接口ChannelPipeline的提供的方法,接下来我们分析一下其实现类DefaultChannelPipeline具 ...

随机推荐

  1. 自动配置的Springboot内junit测试单元不能运行

    解决测试单元不能运行 问题:测试单元的 @Test 前面没有运行图标 解决 IDEA内:File - Setting - Plugins:搜到JUnitGenerator2.0,安装,重启IDEA 光 ...

  2. Centos7中磁盘管理及扩展

    前提要求: 虚拟机:centos7 虚拟机软件:VMware Workstation 12 在安装Centos系统时,磁盘选择为LVM逻辑卷.当选择为LVM后才能创建逻辑卷等(必须) 数据格式选择的是 ...

  3. Asp.net MVC验证那些事(1)-- 介绍和验证规则使用----[转]--[并修改了部分内容]

    Asp.net MVC验证那些事(1)-- 介绍和验证规则使用 -----原文地址链接 数据的有效性验证,是程序开发中必不可少的环节.这篇文章,我们将用一个实例来说明如何在MVC中使用Validati ...

  4. Spring Beans 自动装配 简介

    Beans 自动装配 Spring 容器可以在不使用<constructor-arg>和<property> 元素的情况下自动装配相互协作的 bean 之间的关系,这有助于减少 ...

  5. Hyperledger Fabric——balance transfer(四)安装和实例化chaincode

    详细解析blance transfer示例的安装(install)和实例化(Instantiate)链码(chaincode)的过程.安装chaincode会根据本地的链码文件生成chaincode镜 ...

  6. 【MySQL】覆盖索引和回表

    先来了解一下两大类索引 聚簇索引(也称聚集索引,主键索引等) 普通索引(也成非聚簇索引,二级索引等) 聚簇索引 如果表设置了主键,则主键就是聚簇索引 如果表没有主键,则会默认第一个NOT NULL,且 ...

  7. Palindromes _easy version(hdu2029)

    输入格式:首先一个整型,然后循环不带空格未知长度的字符串. 思考:首先用scanf_s()输入整型,然后一个大循环,用gets_s()函数输入字符串. 注意:scanf_s()多加了一个%c,& ...

  8. Pyqt5_Python运用过程中一些问题和技巧

    安装python3&pyqt5 1.         网下载python3.7安装包,安装时选择自定义安装,勾选上PIP 直接去官网上下载,一路下一步就可以了,然后将D:\Python37.D ...

  9. springboot使用Jwt处理跨域认证问题

    在前后端开发时为什么需要用户认证呢?原因是由于HTTP协定是不存储状态的,这意味着当我们透过账号密码验证一个使用者时,当下一个request请求时他就把刚刚的资料忘记了.于是我们的程序就不知道谁是谁了 ...

  10. PHP cookie基本操作

    PHP中Cookie的使用---添加/更新/删除/获取Cookie 及 自动填写该用户的用户名和密码和判断是否第一次登陆 什么是cookie 服务器在客户端保存用户的信息,比如登录名,密码等 这些数据 ...