长久以来大家对于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. win10下配置php环境变量

    下载php,解压. 比如解压到E盘,目录为"E:\PHP". 打开php目录,复制一个php.ini-development文件,改名为php.ini. 打开php.ini,将 e ...

  2. ABP官方文档翻译 8.1 通知系统

    通知系统 介绍 发送模型 通知类型 通知数据 通知严重性 关于通知持久化 订阅通知 发布通知 用户通知管理 实时通知 客户端 通知存储 通知定义 介绍 在系统中通知用来基于特定的事件告知用户.ABP提 ...

  3. centos6.8 安装gitlab记录

    sudo yum install -y curl policycoreutils-python openssh-server cronie sudo lokkit -s http -s ssh sud ...

  4. 使用exe4j工具制作简单的java应用程序

    首先需要下载exe4j工具并进行安装,下面是利用exe4j工具制作应用程序的步骤. 1.首先将工程导出为可运行的jar包,选择extract required libralies into gener ...

  5. Cookie、session和localStorage、以及sessionStorage之间的区别

    一.Cookie.session和localStorage的区别 cookie的内容主要包括:名字.值.过期时间.路径和域.路径与域一起构成cookie的作用范围.若不设置时间,则表示这个cookie ...

  6. 洛谷 [P2764]最小路径覆盖问题

    二分图应用模版 #include <iostream> #include <cstdio> #include <algorithm> #include <cs ...

  7. Google chrome浏览器中通过扩展调用本地应用程序以及和程序相互通讯(C++)

    最近项目用到浏览插件的开发,IE用到的是BHO,chrome打算做成扩展. 但是和ie有一点不同,chrome扩展是基于html+js+css开发的,那么就会有二个问题 1. 代码和算法等容易被别人复 ...

  8. HttpGet HttpPost

    public string HttpGet(string Url, string postDataStr) { HttpWebRequest request = (HttpWebRequest)Web ...

  9. [Python Study Notes]匿名函数

    Python 使用 lambda 来创建匿名函数. lambda这个名称来自于LISP,而LISP则是从lambda calculus(一种符号逻辑形式)取这个名称的.在Python中,lambda作 ...

  10. Windows Server 2016-抢占FSMO角色

    很多情况下,当生产域控制器发生问题无法修复的情况下,我们只能通过抢占FSMO角色以保证用户验证等正常或及时恢复.一般在同一个域环境中,我们往往都会有主备或主辅域控规划,平时工作的时候,两台域控可以实现 ...