长久以来大家对于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的正确使用相关推荐

  1. auto.js停止所有线程_Java线程与并发编程实践:深入理解volatile和final变量

    同步有两种属性:互斥性和可见性.synchronized关键字与两者都有关系.Java同时也提供了一种更弱的.仅仅包含可见性的同步形式,并且只以volatile关键字关联. 假设你自己设计了一个停止线 ...

  2. Java并发编程实战_一线大厂架构师整理:java并发编程实践教程

    并发编程是Java语言的重要特性之一, 在Java平台上提供了许多基本的并发功能来辅助开发多线程应用程序.然而,这些相对底层的并发功能与上层应用程序的并发语义之间并不存在一种简单而直观的映射关系.因此 ...

  3. java并发编程实践_Java并发编程实践如何正确使用Unsafe

    一.前言 Java 并发编程实践中的话: 编写正确的程序并不容易,而编写正常的并发程序就更难了.相比于顺序执行的情况,多线程的线程安全问题是微妙而且出乎意料的,因为在没有进行适当同步的情况下多线程中各 ...

  4. java并发编程实践(2)线程安全性

    [0]README 0.0)本文部分文字描述转自:"java并发编程实战", 旨在学习"java并发编程实践(2)线程安全性" 的相关知识: 0.1)几个术语( ...

  5. JAVA并发编程实践笔记

    2019独角兽企业重金招聘Python工程师标准>>> JAVA并发编程实践笔记 博客分类: java JAVA并发编程实践笔记 1, 保证线程安全的三种方法:     a, 不要跨 ...

  6. 《Java线程与并发编程实践》—— 2.6 小结

    本节书摘来异步社区<Java线程与并发编程实践>一书中的第2章,第2.6节,作者: [美]Jeff Friesen,更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

  7. [Java 并发] Java并发编程实践 思维导图 - 第一章 简单介绍

    阅读<Java并发编程实践>一书后整理的思维导图.

  8. 《Java并发编程实践》学习笔记之一:基础知识

    <Java并发编程实践>学习笔记之一:基础知识 1.程序与进程 1.1 程序与进程的概念 (1)程序:一组有序的静态指令,是一种静态概念:  (2)进程:是一种活动,它是由一个动作序列组成 ...

  9. 并发编程实践三:Condition

    Condition实例始终被绑定到一个锁(Lock)上.Lock替代了Java的synchronized方法,而Condition则替代了Object的监视器方法,包含wait.notify和noti ...

  10. 《Java线程与并发编程实践》—— 2.3 谨防活跃性问题

    本节书摘来异步社区<Java线程与并发编程实践>一书中的第2章,第2.3节,作者: [美]Jeff Friesen,更多章节内容可以访问云栖社区"异步社区"公众号查看. ...

最新文章

  1. 堆(heap)和栈(stack)有什么区别
  2. ubuntu16.04装机:网易云+搜狗拼音+chrome+uGet+caffe(openCV3.1+CUDA+cuDNN+python)
  3. deb 安装_本地安装DEB包的3种命令行工具(适合Ubuntu和Deepin )
  4. QT的QSslPreSharedKeyAuthenticator类的使用
  5. 1_4 BuilderMode 建造者模式
  6. java stream 原理
  7. wordpress html音乐,WordPress引用百度Ting音乐方法
  8. PHP未来码支付V1.3网站源码开源版
  9. java 父子级json组装不用递归_揭秘java中无数人伤透脑筋最为神秘的技术之一——ClassLoader...
  10. java获取配置文件_JAVA读取配置文件的方法
  11. 时钟转盘html源代码
  12. c语言看门狗的作用,AT89S52单片机看门狗C语言程序
  13. 不是技术牛人,如何进去自己梦想的公司
  14. 编译指示_#pragma在DSP中的编程技巧
  15. 中心移动平均_移动流量资费再降20%以上!
  16. 短文:U盘物理写保护原理
  17. 【第93期】谁是元宇宙的“基础设施”?
  18. qt mingw32编译项目报错:Nothing to be done for 'first'.
  19. RSD 教程 —— §3.3 观察图像
  20. 会员系统_健身房管理系统

热门文章

  1. 第1章 Spring Cloud 构建微服务架构(一)服务注册与发现
  2. E - 娜娜梦游仙境系列——莫名其妙的插曲
  3. jsp 基本语法学习笔记
  4. JScrollPane恢复正常滚动量
  5. Struts2.X深入浅出 学习笔记
  6. LeetCode每日一题——串联字符串的最大长度
  7. 【已解决】bootstrap table 参数后台获取不到
  8. 面向对象编程 --- 反射
  9. Fragment控件初始化
  10. Python学习笔记五