Netty的并发编程实践2:volatile的正确使用
长久以来大家对于volatile如何正确使用有很多的争议,既便是一些经验丰富的Java设计师,对于volatile和多线程编程的认识仍然存在误区。其实,volatile的使用非常简单,只要理解了Java的内存模型和多线程编程的基础知识,正确使用volatile是不存在任何问题的。下面我们结合Netty的源码,对volatile的正确使用进行说明。
打开NioEventLoop的代码,我们来看控制I/O操作和其他任务运行比例的ioRatio,它是int类型的变量,定义如下。
我们发现,它被定义为volatile,为什么呢?我们首先对volatile关键字进行说明,然后再结合Netty的代码进行分析。
关键字volatile是Java提供的最轻量级的同步机制,Java内存模型对volatile专门定义了一些特殊的访问规则。下面我们就看它的规则。
当一个变量被volatile修饰后,它将具备以下两种特性。
◎ 线程可见性:当一个线程修改了被volatile修饰的变量后,无论是否加锁,其他线程都可以立即看到最新的修改,而普通变量却做不到这点。
◎ 禁止指令重排序优化,普通的变量仅仅保证在该方法的执行过程中所有依赖赋值结果的地方都能获取正确的结果,而不能保证变量赋值操作的顺序与程序代码的执行顺序一致。举个简单的例子说明下指令重排序优化问题,如图21-5所示。
图21-5 指令重排序和优化导致线程无法退出
我们预期程序会在3s后停止,但是实际上它会一直执行下去,原因就是虚拟机对代码进行了指令重排序和优化,优化后的指令如下。
if (!stop)
While(true)
......
重排序后的代码是无法发现stop被主线程修改的,因此无法停止运行。要解决这个问题,只要将stop前增加volatile修饰符即可。代码修改如图21-6所示。
再次运行,我们发现3s后程序退出,达到了预期效果,使用volatile解决了如下两个问题。
◎ main线程对stop的修改在workThread线程中可见,也就是说workThread线程立即看到了其他线程对于stop变量的修改。
◎ 禁止指令重排序,防止因为重排序导致的并发访问逻辑混乱。
一些人认为使用volatile可以代替传统锁,提升并发性能,这个认识是错误的。volatile仅仅解决了可见性的问题,但是它并不能保证互斥性,也就是说多个线程并发修改某个变量时,依旧会产生多线程问题。因此,不能靠volatile来完全替代传统的锁。
图21-6 volatile解决指令重排序和编译优化问题
根据经验总结,volatile最适合使用的是一个线程写,其他线程读的场合,如果有多个线程并发写操作,仍然需要使用锁或者线程安全的容器或者原子变量来代替。
讲了volatile的原理之后,我们继续对Netty的源码做分析。上面讲到了ioRatio被定义成volatile,下面看看代码为什么要这样定义。参见如图21-7所示代码。
图21-7 volatile在NioEventLoop线程中的应用
通过代码分析我们发现,在NioEventLoop线程中,ioRatio并没有被修改,它是只读操作。既然没有修改,为什么要定义成volatile呢?继续看代码,我们发现NioEventLoop提供了重新设置I/O执行时间比例的公共方法,接口如图21-8所示。
图21-8 修改volatile变量
首先,NioEventLoop线程没有调用该方法,说明调整I/O执行时间比例是外部发起的操作,通常是由业务的线程调用该方法,重新设置该参数。这样就形成了一个线程写、一个线程读。根据前面针对volatile的应用总结,此时可以使用volatile来代替传统的synchronized关键字提升并发访问的性能。
Netty中大量使用了volatile来修改成员变量,如果理解了volatile的应用场景,读懂Netty volatile的相关代码还是比较容易的。
Netty的并发编程实践2:volatile的正确使用的更多相关文章
- Netty的并发编程实践4:线程安全类的应用
在JDK1.5的发行版本中,Java平台新增了java.util.concurrent,这个包中提供了一系列的线程安全集合.容器和线程池,利用这些新的线程安全类可以极大地降低Java多线程编程的难度, ...
- Netty的并发编程实践3:CAS指令和原子类
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为阻塞同步,它属于一种悲观的并发策略,我们称之为悲观锁.随着硬件和操作系统指令集的发展和优化,产生了非阻塞同步,被称为 ...
- Netty的并发编程实践1:正确使用锁
很多刚接触多线程编程的开发者,虽然意识到了并发访问可变变量需要加锁,但是对于锁的范围.加锁的时机和锁的协同缺乏认识,往往会导致出现一些问题.下面笔者就结合Netty的代码来讲解下这方面的知识. 打开F ...
- Netty的并发编程实践5:不要依赖线程优先级
当有多个线程同时运行的时候,由线程调度器来决定哪些线程运行.哪些等待以及线程切换的时间点,由于各个操作系统的线程调度器实现大相径庭,因此,依赖JDK自带的线程优先级来设置线程优先级策略的方法是错误和非 ...
- 并发编程实践五:ReentrantLock
ReentrantLock是一个可重入的相互排斥锁,实现了接口Lock,和synchronized相比,它们提供了同样的功能.但ReentrantLock使用更灵活.功能更强大,也更复杂.这篇文章将为 ...
- Java并发编程实践
最近阅读了<Java并发编程实践>这本书,总结了一下几个相关的知识点. 线程安全 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任 ...
- [Java 并发] Java并发编程实践 思维导图 - 第一章 简单介绍
阅读<Java并发编程实践>一书后整理的思维导图.
- [Java 并发] Java并发编程实践 思维导图 - 第二章 线程安全性
依据<Java并发编程实践>一书整理的思维导图.
- 并发编程实践三:Condition
Condition实例始终被绑定到一个锁(Lock)上.Lock替代了Java的synchronized方法,而Condition则替代了Object的监视器方法,包含wait.notify和noti ...
随机推荐
- 异常检测算法:Isolation Forest
iForest (Isolation Forest)是由Liu et al. [1] 提出来的基于二叉树的ensemble异常检测算法,具有效果好.训练快(线性复杂度)等特点. 1. 前言 iFore ...
- 使用zii.widgets.CDetailView显示内容
Yii里的CDetailView可以用来显示详细内容,有时会遇到显示的html内容是被转义过的,也就是原本是要显示html样式的,结果显示出来的内容却是把html当作普通文本了. 先看一个CDetai ...
- 洛谷 [P1020] 导弹拦截 (N*logN)
首先此一眼就能看出来是一个非常基础的最长不下降子序列(LIS),其朴素的 N^2做法很简单,但如何将其优化成为N*logN? 我们不妨换一个思路,维护一个f数组,f[x]表示长度为x的LIS的最大的最 ...
- BZOJ 3198: [Sdoi2013]spring [容斥原理 哈希表]
3198: [Sdoi2013]spring 题意:n个物品6个属性,求有多少不同的年份i,j满足有k个属性对应相等 一开始读错题了,注意是对应相等 第i个属性只能和第i个属性对应 容斥一下 \[ 恰 ...
- 模拟器配置使用Fildder进行抓包,包含Https+证书
1.首先,百度检索.参考别人的,大致上都是到安装证书就失败了.我后面只说几个关键点. 2.安装证书,必须设置屏幕密码.我最开始使用把cef拷贝到,手机结果出现bug,安装不了.后来采用了在手机内部访问 ...
- 读论文系列:Object Detection SPP-net
本文为您解读SPP-net: Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition Motivat ...
- Eclipse Java,debug模式无法调试,调试按钮不可用时解决办法
经常出现debug模式进入后,debug的几个按钮置灰,F5,6,8,没有任何反应时,这样操作: 退出 Eclipse.打开 Eclipse 目录下的 configuration 下的 org.ecl ...
- LNMP环境下搭建wordpress
WordPress 下载WordPress安装包,可以直接wget获取也可以ftp上传,解压到/usr/share/nginx/html/blog-wp,访问index.php即进行安装: wor ...
- 编译安装python3.6后pip3无法安装模块问题处理
编译安装python3.6之后,使用pip3命令安装第三方库效果如图所示: pip is configured with locations that require TLS/SSL, however ...
- ci框架基础知识点
一.路由 1.index.php/test/hello->控制器test的hello方法 2. 也可以手动配置路由 app/config/routes.php中 I:$route[' ...