长久以来大家对于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. Netty的并发编程实践4:线程安全类的应用

    在JDK1.5的发行版本中,Java平台新增了java.util.concurrent,这个包中提供了一系列的线程安全集合.容器和线程池,利用这些新的线程安全类可以极大地降低Java多线程编程的难度, ...

  2. Netty的并发编程实践3:CAS指令和原子类

    互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能的额外损耗,因此这种同步被称为阻塞同步,它属于一种悲观的并发策略,我们称之为悲观锁.随着硬件和操作系统指令集的发展和优化,产生了非阻塞同步,被称为 ...

  3. Netty的并发编程实践1:正确使用锁

    很多刚接触多线程编程的开发者,虽然意识到了并发访问可变变量需要加锁,但是对于锁的范围.加锁的时机和锁的协同缺乏认识,往往会导致出现一些问题.下面笔者就结合Netty的代码来讲解下这方面的知识. 打开F ...

  4. Netty的并发编程实践5:不要依赖线程优先级

    当有多个线程同时运行的时候,由线程调度器来决定哪些线程运行.哪些等待以及线程切换的时间点,由于各个操作系统的线程调度器实现大相径庭,因此,依赖JDK自带的线程优先级来设置线程优先级策略的方法是错误和非 ...

  5. 并发编程实践五:ReentrantLock

    ReentrantLock是一个可重入的相互排斥锁,实现了接口Lock,和synchronized相比,它们提供了同样的功能.但ReentrantLock使用更灵活.功能更强大,也更复杂.这篇文章将为 ...

  6. Java并发编程实践

    最近阅读了<Java并发编程实践>这本书,总结了一下几个相关的知识点. 线程安全 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任 ...

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

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

  8. [Java 并发] Java并发编程实践 思维导图 - 第二章 线程安全性

    依据<Java并发编程实践>一书整理的思维导图.

  9. 并发编程实践三:Condition

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

随机推荐

  1. Git知识总览(五) Git中的merge、rebase、cherry-pick以及交互式rebase

    上篇博客聊了<git分支管理之rebase 以及 cherry-pick相关操作>本篇博客我们就以Learning Git中的关卡进行展开.下方列举了LearningGit中的 merge ...

  2. Jmeter_打印当前时间戳&打印偏移时间戳

    Jmeter中提供了一种函数,可以打印时间戳,如下图 年: yyyy 月:MM 日:dd 时: HH 分: mm 秒:ss 关于时间戳的格式,可以自由组合定义,这里我写成这样 yyyy-MM-dd H ...

  3. 不要用for循环去遍历LinkedList

    ArrayList与LinkedList的普通for循环遍历 对于大部分Java程序员朋友们来说,可能平时使用得最多的List就是ArrayList,对于ArrayList的遍历,一般用如下写法: p ...

  4. JavaScript之父谈JavaScript

    本文翻译自popularity,为了更好的阅读我把部分内容进行了增删改,如果你英语比较好,建议直接阅读原文,因为这篇文章是我通过google翻译再进行修改的. 貌似(根据一位精神导师的说法)JavaS ...

  5. 二维码开源库ZBar-windows下编译和使用

    源码 下载最新Zbar源码(http://zbar.sourceforge.net/),网站的WIKI是空白的,所以只能在源码包里找使用说明了,很遗憾Windows下怎么编译没说明,只是说明了Wind ...

  6. LeetCode - 540. Single Element in a Sorted Array

    Given a sorted array consisting of only integers where every element appears twice except for one el ...

  7. WPF字典集合类ObservableDictionary

    WPF最核心的技术优势之一就是数据绑定.数据绑定,可以通过对数据的操作来更新界面. 数据绑定最经常用到的是ObservableCollection<T> 和 Dictionary<T ...

  8. hibernate监听器的应用

    这里是我看到的一个hibernate监听器的简单实现供参考  http://www.360doc.com/content/14/0623/11/8072791_389034447.shtml 设计思路 ...

  9. mac 查看某个文件夹下所有隐藏文件(夹)的大小

    du -d 1 -h 对应Linux的命令是: du -ah --max-depth=1

  10. PHPUnit 手册

    PHPUnit 手册 Sebastian Bergmann 版权 © 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015 ...