一个多线程reactor模型的bug:线程安全一定要把构造方法考虑在内

牛有肉 牛有肉     2022-12-26     728

关键词:

 

  众所周知,JVM 创建一个对象分三步:

  1.在堆内存开辟内存空间。

  2.在堆内存中实例化Car里面的各个参数。

  3.把对象指向堆内存空间。

  为了提高运行效率,编译器在编译代码时可能会对指令进行重排序。重排序的原则是,保证单线程执行结果的正确性,并遵循 happen-before 原则。

  指令间的依赖关系包括数据依赖和控制依赖。对于控制依赖,处理器 本身存在流水线冒险等行为,处理器层面会对存在控制依赖的指令进行结果检查,该类指令优化不在编译器层面。而对于存在数据依赖的指令,如果打乱顺序,显然会造成单线程情况下执行结果的变化。所以编译器不会打乱存在数据依赖的指令的顺序。

  上述三步中的 2、3 两步并不存在数据依赖关系,单线程情况下将其顺序颠倒并不会导致执行结果的变化,并且这两步不在 happen-before 原则范围内,因此在编译过程中可能会被打乱顺序。

  但在多线程环境下,这两步是可能存在数据依赖关系的。

  比如一个比较复杂的例子,NIO 编程时,我们为 channel 注册了一个感兴趣的事件,并绑定一个 handler 实例:

   如果事件触发,将会在一个新线程执行 handler 中的 run 方法,handler 的构造方法如下:

   构造方法会初始化成员变量。run 方法中会使用这些变量:

   如果在第一张图中,new Handler 时,2、3 步 被乱序执行,那么第三张图 read 函数调用会报空指针异常。因为指向 handler 对象存储空间的指针首先被返回赋值给了 channel 的 attachment ,但 handler 对象的成员还没有被初始化,所以 成员变量 socketChannel 还是空的。

  事实也证实了这一点:

  我们要保证的是,执行 handler 的 run 之前,handler 的构造方法得到完全执行,并且多核环境下执行结果对其它线程(核心)可见。

  因此多线程环境下,想要程序按预想的情况执行,我们需要保证两点:

  1. 构造函数的执行早于 run 函数的执行。

  2. 构造函数必须完全执行,且结果对所有线程可见,才可以执行 run 方法。

  对于 1, 我们在执行 run 之前可以对 attachment 进行判空,空的话什么都不做,等待事件下次触发再判断构造函数是否已经执行(感谢 NIO 的水平触发)。

  对于 2,我们在 1 的基础上,通过锁使得 构造函数 与 run 方法互斥即可实现。通过 synchronized 的锁和其 monitorEnter 后、monitorExit 前的内存屏障可以充分保证可见性和有序性。一旦构造函数先执行,run 一定等待其执行完成才会执行;若构造函数未执行,run 立即返回等待下次被触发。

  构造函数加锁:

  run 函数加锁:

    在写多线程代码时,如果存在类成员变量等线程共享变量,一定要注意其线程安全性。在写上述代码时(一个 Reactor 模型),因为一个 handler 对象只会被一个线程调用,因此忽略了对线程安全的考虑。但最后出错才发现,还有另一个线程在调用构造方法!这样便是两个线程操作 handler 对象,一个调用构造函数、一个调用其它函数。

  考虑线程安全问题,构造方法一定要考虑在内!

  作如上修改后,问题解决,大剂量测试没有异常发生:

 

吃透redis:网络框架篇-reactor模型

文章目录演进Reactor模型单Reactor单线程单Reactor多线程多Reactor多进程Proactor模型演进如果要让服务器服务多个客户端,那么最直接的方式就是为每一条连接创建线程。其实创建进程也是可以的,原理是一样的,进程和线... 查看详情

吃透redis:网络框架篇-reactor模型

文章目录演进Reactor模型单Reactor单线程单Reactor多线程多Reactor多进程Proactor模型演进如果要让服务器服务多个客户端,那么最直接的方式就是为每一条连接创建线程。其实创建进程也是可以的,原理是一样的,进程和线... 查看详情

netty的reactor多线程模型,nioeventloop,channelpipeline简介

...找到的一大批文章,你大概率会看到这张图,外加关键字NIO,Reactor多线程模型,异步串行无锁化,堆外内存,pipeline,翻看完这些文章后可以让你对Netty的原理有大致了解,但是Netty如何实现这些的呢?本文将尽可能简单的解释Netty中Reactor多... 查看详情

netty知识图谱

Nettynetty的线程模型Reactor模式类型单线程ReactorReactor是一个线程对象,该线程会启动事件循环,并使用Selector(选择器)来实现IO的多路复用。注册一个Acceptor事件处理器到Reactor中,Acceptor事件处理器所关注的事件是ACCEPT事... 查看详情

netty知识图谱

Nettynetty的线程模型Reactor模式类型单线程ReactorReactor是一个线程对象,该线程会启动事件循环,并使用Selector(选择器)来实现IO的多路复用。注册一个Acceptor事件处理器到Reactor中,Acceptor事件处理器所关注的事件是ACCEPT事... 查看详情

netty知识图谱

Nettynetty的线程模型Reactor模式类型单线程ReactorReactor是一个线程对象,该线程会启动事件循环,并使用Selector(选择器)来实现IO的多路复用。注册一个Acceptor事件处理器到Reactor中,Acceptor事件处理器所关注的事件是ACCEPT事... 查看详情

reactor(死磕2)(代码片段)

【正文】netty源码 死磕2: 传说中神一样的Reactor反应器模式本文目录1.为什么是Reactor模式2.Reactor模式简介3.多线程IO的致命缺陷4.单线程Reactor模型4.1.什么是单线程Reactor呢?4.2.单线程Reactor的参考代码4.3.单线程模式的缺点:5.... 查看详情

6.netty线程模型-reactor(代码片段)

...自:[Netty]Netty'sthreadmodelandsimpleusage2.netty模型是以Reactor模式为基础的,具体的,netty使用的是主从Reactor多线程模型;3.先介绍了Reactor线程模型;后介绍了Netty组成部分;4.文末还po出了 查看详情

netty

...快速入门Netty概述Netty的线程模型概述**Netty服务模型**单Reactor单线程单Reactor多线程主从Reactor多线程Netty模型简单版进阶版详细版 查看详情

两种高效的事件处理模式(proactor和reactor)

典型的多线程服务器的线程模型   1. 每个请求创建一个线程,使用阻塞式 I/O 操作   这是最简单的线程模型,1个线程处理1个连接的全部生命周期。该模型的优点在于:这个模型足够简单,它可... 查看详情

nio多路复用的终极奥义

...最有效的方式就是使用事件驱动模型进行异步调用。3、Reactor模型就是基于事件驱动的一个多路复用模型,它又可分为单线程、多线程、主从式的Reactor模型,以应对不同应用场景,Nginx、Redis、Netty都采用了Reactor模型作为多路复... 查看详情

不明显的多线程编程的具体bugs

...应该记住很多细节,比如锁,使用线程安全库等。这里有一个不太明显的bug的列表,特定于多线程程序。其中许多都没有在初学者的文档或教程中提到,但我认为每个使用线程的人最终都会中枪。使用theadsafe系统函数    并... 查看详情

io流中「线程」模型总结(代码片段)

...2、参考案例四、异步非阻塞1、模型图解2、参考案例五、Reactor模型1、模型图解1.1Reactor设计原理1.2单Reactor单线程1.3单Reactor多线程1.4主从Reactor多线程2、参考案例六、参考源码IO流模块:经常看、经常用、经常忘;一、基础简介在... 查看详情

netty-线程模型reactor

目录线程模型1、传统IO服务模型2、Reactor模式reactor三种模式:总结netty模型执行流程简述线程模型1、传统IO服务模型阻塞的IO模式获取输入数据,每个链接需要独立的线程完成数据传输及处理当并发数较大时,创建大... 查看详情

netty-线程模型reactor

目录线程模型1、传统IO服务模型2、Reactor模式reactor三种模式:总结netty模型执行流程简述线程模型1、传统IO服务模型阻塞的IO模式获取输入数据,每个链接需要独立的线程完成数据传输及处理当并发数较大时,创建大... 查看详情

网络io模型reactor模式(代码片段)

/***Reactor模式简述**Reactor负责轮询selector,将就绪事件分发给handler处理。*handler大致有两种:*1.acceptor:负责建连,建连后注册iohandler;*2.iohandler:负责处理io读写事件;**所以Reactor模式是一种事件响应... 查看详情

java多线程之线程安全(重点,难点)(代码片段)

...安全1.线程不安全的原因:1.1抢占式执行1.2多个线程修改同一个变量1.3修改操作不是原子的锁(synchronized)1.一个锁对应一个锁对象.2.多个锁对应一个锁对象.2.多个锁对应多个锁对象.4.找出代码错误5.锁的另一种用法1.4内存可见性解决... 查看详情

reactor模型基本并发编程模型

Reactor模型(一)基本并发编程模型Netty系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)在讲解Reactor线程模型之前,我们需要先对基本并发编程模型:串行工作模型、并发工作模型进行讲解。串行工作者模型和并行工作者模型关... 查看详情