一、目录

  1、引入话题-发散思考

  2、volatile深度解析

  3、解决volatile原子性问题

  4、volatile应用场景

二、引入话题-发散思考

public class T1 {
/*volatile*/ boolean running=true;
 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
System.out.println(Thread.currentThread().getName()</span>+":start!"<span style="color: #000000;">);
</span><span style="color: #0000ff;">while</span><span style="color: #000000;">(running){
</span><span style="color: #008000;">/*</span><span style="color: #008000;">try {
TimeUnit.MINUTES.sleep(2);
} catch (Exception e) {
e.printStackTrace();
}</span><span style="color: #008000;">*/</span><span style="color: #000000;">
}
System.out.println(Thread.currentThread().getName()</span>+":end!"<span style="color: #000000;">);
} </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
T1 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T1(); </span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"t"<span style="color: #000000;">).start();
</span><span style="color: #0000ff;">try</span><span style="color: #000000;"> {
TimeUnit.SECONDS.sleep(</span>1<span style="color: #000000;">);
} </span><span style="color: #0000ff;">catch</span><span style="color: #000000;"> (Exception e) {
e.printStackTrace();
}
t.running</span>=<span style="color: #0000ff;">false</span><span style="color: #000000;">;
}

}

运行结果:

无volatile:

t:start!

有volatile:

t:start!

t:end!

含有volatile是期望的结果,那为什么不添加volatile会产生这种情况呢?
再谈Java内存模型:
     在虚拟机中,堆内存用于存储共享数据(实例对象),堆内存也就是这里说的主内存。
     每个线程将会在堆内存中开辟一块空间叫做线程的工作内存,附带一块缓存区用于存储共享数据副本。那么,共享数据在堆内存当中,线程通信就是通过主内存为中介,线程在本地内存读并且操作完共享变量操作完毕以后,把值写入主内存。
 
解析无volatile:
  • 根据上述java内存模型可知,最开始running=true在主存中,开启线程A,线程会把主存的running=true复制一份写入工作内存的共享变量副本中。
  • 当我们改变running=false,在主存中已经发生改变。
  • 线程A一直在工作状态,没有空闲时间去知道主存的情况,而是一直在读本地内存的共享变量副本,也就一直running=true,取而代之也会产生上述情况。

三、volatile深度解析

那为什么含有volatile就能及时刷新工作内存呢?它有什么作用呢?
volatile:可见性(一个线程修改共享变量以后,立马会被其他线程知可见)、禁止重排序。
 
1、什么是可见性?
虚拟机的happens-before中的volatile规则:volatile变量写操作先于读操作,一个线程去读取volatile变量,另一个线程去写volatile变量,那么volatile变量的写操作优先。
  • 根据上述java内存模型可知,最开始running=true在主存中,开启线程A,线程会把主存的running=true复制一份写入工作内存的共享变量副本中。
  • 当我们改变running=false,在主存已经发生改变。
  • 就在这时,当主存与工作内存发生不一致的时候,工作内存的共享变量会失效,那么工作内存就会去主存刷新一遍共享变量,所以running=false,自然就执行下面的代码啦!
2、什么是禁止重排序?
先谈有序性:
int a=1;
int b =3;
int c=a*b;
在虚拟机中,执行上述代码,一定是按照上述顺序执行吗?那可不一定,像a=1,b=3的顺序完全可能先执行b=3,a=1,这被称为重排序;但是c=a*b一定在a=1,b=3后面,这被成为有序性。
 
再谈重排序:
//线程1:
context = loadContext(); //语句1
inited = true; //语句2 //线程2:

while(!inited ){

sleep()

}

doSomethingwithconfig(context);

假设上述代码在单线程中,谈过重排序不会对代码造成什么影响,但是我们看这一段代码。
语句1与语句2并没有太多的依赖关系,参考有序性例子,那么他们就可以重排序,那么可能语句2执行先于语句1,假设在线程1中语句2执行完就刷新inited到主存,还没等语句1执行呢?线程2就执行起来,一看inited=true,挑出循环,执行下面的代码,context=null就报出空指针,显然这是不能被虚拟机允许的。
 
所以,volatile明确规定禁止重排序,意思就是context=loadContext必须先于inited执行。
在虚拟机中设定了有序性,也就是前面谈到的happens-before原则。如果不满足上该原则的情况下,虚拟机是可以自由的重排序的,下面附录此规则。
 
3、先行发生原则(happens-before)
    • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
    • 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
    • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
    • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
    • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
    • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
    • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
    • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

四、解决volatile原子性问题

1、volatile能解决原子性问题吗?什么是原子性呢,本不想解释,为了读者能够更透彻理解,再解释一下。

原子性:只有一个线程访问共享数据,也就是当线程A访问一个代码块的时候,其他线程全部堵塞,只有等代码块全部执行完,才能被其他线程访问共享数据。
public class T2 {
volatile int count=0;
 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
</span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;1000;i++<span style="color: #000000;">)
count</span>++<span style="color: #000000;">;
} </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
T2 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T2();
List</span>&lt;Thread&gt; threads=<span style="color: #0000ff;">new</span> ArrayList&lt;Thread&gt;<span style="color: #000000;">(); </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;10;i++<span style="color: #000000;">){
threads.add(</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"thread-"+<span style="color: #000000;">i));
} threads.forEach((o)</span>-&gt;<span style="color: #000000;">o.start()); </span><span style="color: #008000;">//</span><span style="color: #008000;">等待所有线程都执行完</span>
threads.forEach((o)-&gt;<span style="color: #000000;">o.yield()); System.out.println(</span>"count:"+<span style="color: #000000;">t.count);
}

}

运行结果:

count:8710 //每次都不一样。

 2、为什么加了volatile还是不能得到预期结果呢?因为它只保证了可见性,不能保证原子性。what?

再回忆java内存模型:

 

3、那怎么解决呢?

方式一:synchronized,jvm对synchronized进行了很大的优化,所以效率也没有想象中那么低。

public class T3 {
int count=0;
 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">synchronized</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
</span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;1000;i++<span style="color: #000000;">)
count</span>++<span style="color: #000000;">;
} </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
T3 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T3();
List</span>&lt;Thread&gt; threads=<span style="color: #0000ff;">new</span> ArrayList&lt;Thread&gt;<span style="color: #000000;">(); </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;10;i++<span style="color: #000000;">){
threads.add(</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"thread-"+<span style="color: #000000;">i));
} threads.forEach((o)</span>-&gt;<span style="color: #000000;">o.start()); </span><span style="color: #008000;">//</span><span style="color: #008000;">等待所有线程都执行完</span>
threads.forEach((o)-&gt;<span style="color: #000000;">o.yield()); System.out.println(</span>"count:"+<span style="color: #000000;">t.count);
}

}

方式二:ReentrantLock,跟synchronized的作用差不多。

public class T5 {
ReentrantLock lock=new ReentrantLock();
int count=0;
 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
lock.lock();
</span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;1000;i++<span style="color: #000000;">)
count</span>++<span style="color: #000000;">;
lock.unlock();
} </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
T4 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T4();
List</span>&lt;Thread&gt; threads=<span style="color: #0000ff;">new</span> ArrayList&lt;Thread&gt;<span style="color: #000000;">(); </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;10;i++<span style="color: #000000;">){
threads.add(</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"thread-"+<span style="color: #000000;">i));
} threads.forEach((o)</span>-&gt;<span style="color: #000000;">o.start()); </span><span style="color: #008000;">//</span><span style="color: #008000;">等待所有线程都执行完</span>
threads.forEach((o)-&gt;<span style="color: #000000;">o.yield()); System.out.println(</span>"count:"+<span style="color: #000000;">t.count);
}

}

方式三:AtomicInteger原子类
public class T4 {
AtomicInteger count=new AtomicInteger(0);
 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> m(){
</span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;1000;i++<span style="color: #000000;">)
count.getAndIncrement();
} </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> main(String[] args) {
T4 t</span>=<span style="color: #0000ff;">new</span><span style="color: #000000;"> T4();
List</span>&lt;Thread&gt; threads=<span style="color: #0000ff;">new</span> ArrayList&lt;Thread&gt;<span style="color: #000000;">(); </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i=0;i&lt;10;i++<span style="color: #000000;">){
threads.add(</span><span style="color: #0000ff;">new</span> Thread(()-&gt;t.m(),"thread-"+<span style="color: #000000;">i));
} threads.forEach((o)</span>-&gt;<span style="color: #000000;">o.start()); </span><span style="color: #008000;">//</span><span style="color: #008000;">等待所有线程都执行完</span>
threads.forEach((o)-&gt;<span style="color: #000000;">o.yield()); System.out.println(</span>"count:"+<span style="color: #000000;">t.count);
}

}

五、volatile应用场景

说到这里,读者可能就已经懵逼了,这也有问题那也有问题,那我们什么时候用它呢?
volatile是基于synchronized提出的效率优化手段,但是它是不能代替synchronized的。
 
状态标记量:
一般volatile共享变量,不要用于数据计算,最好去标记一些状态值,比如前面说的running=true。
volatile boolean inited = false;
//线程1:
context = loadContext();
inited = true; //线程2:

while(!inited ){

sleep()

}

doSomethingwithconfig(context);

九、版权声明

  作者:邱勇Aaron

  出处:http://www.cnblogs.com/qiuyong/

  您的支持是对博主深入思考总结的最大鼓励。

  本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,尊重作者的劳动成果。

  参考:深入理解JVM、马士兵并发编程、并发编程实践

     volatile关键字解析:http://www.importnew.com/18126.html

并发编程(三):全视角解析volatile的更多相关文章

  1. 并发编程(二):全视角解析volatile

    一.目录 1.引入话题-发散思考 2.volatile深度解析 3.解决volatile原子性问题 4.volatile应用场景 二.引入话题-发散思考 public class T1 { /*vol ...

  2. [Java并发编程(五)] Java volatile 的实现原理

    [Java并发编程(五)] Java volatile 的实现原理 简介 在多线程并发编程中 synchronized 和 volatile 都扮演着重要的角色,volatile 是轻量级的 sync ...

  3. [Java并发编程(四)] Java volatile 的理论实践

    [Java并发编程(四)] Java volatile 的理论实践 摘要 Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 ...

  4. Java并发编程三个性质:原子性、可见性、有序性

      并发编程 并发程序要正确地执行,必须要保证其具备原子性.可见性以及有序性:只要有一个没有被保证,就有可能会导致程序运行不正确  线程不安全在编译.测试甚至上线使用时,并不一定能发现,因为受到当时的 ...

  5. 干货:Java并发编程必懂知识点解析

    本文大纲 并发编程三要素 原子性 原子,即一个不可再被分割的颗粒.在Java中原子性指的是一个或多个操作要么全部执行成功要么全部执行失败. 有序性 程序执行的顺序按照代码的先后顺序执行.(处理器可能会 ...

  6. Java并发编程中的设计模式解析(二)一个单例的七种写法

    Java单例模式是最常见的设计模式之一,广泛应用于各种框架.中间件和应用开发中.单例模式实现起来比较简单,基本是每个Java工程师都能信手拈来的,本文将结合多线程.类的加载等知识,系统地介绍一下单例模 ...

  7. 从缓存入门到并发编程三要素详解 Java中 volatile 、final 等关键字解析案例

    引入高速缓存概念 在计算机在执行程序时,以指令为单位来执行,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入. 由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这 ...

  8. JAVA并发编程:相关概念及VOLATILE关键字解析

    一.内存模型的相关概念 由于计算机在执行程序时都是在CPU中运行,临时数据存在主存即物理内存,数据的读取和写入都要和内存交互,CPU的运行速度远远快于内存,会大大降低程序执行的速度,于是就有了高速缓存 ...

  9. Java并发编程、内存模型与Volatile

    http://www.importnew.com/24082.html  volatile关键字 http://www.importnew.com/16142.html  ConcurrentHash ...

随机推荐

  1. SMP-1

    项目:该项目是用web做一个捐款的管理系统. 目标:可以记录接受捐款和资助捐款的信息,可以查询捐款等. 计划时间:2016-01-01至2016-01-15 实际用时:2016-01-08至2016- ...

  2. 大数据和BI商业智能有何区别?有何相关?

    大数据 ≠BI商业智能,大数据也不是传统商业智能的简单升级. 1.大数据和BI两者的区别 BI(BusinessIntelligence)即商业智能,它是企业数据化管理的一整套的方案,用来将企业中现有 ...

  3. 解析Excel文件并把数据存入数据库

    前段时间做一个小项目,为了同时存储多条数据,其中有一个功能是解析Excel并把其中的数据存入对应数据库中.花了两天时间,不过一天多是因为用了"upload"关键字作为URL从而导致 ...

  4. hdu2059 龟兔赛跑 DP

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2059 虽然 知道是DP ,刚开始一直没有想出状态转移方程. 刚开始的思路就是定义dp[i]表示到达第i ...

  5. git笔记--git@OSC

    之前安装了git,用了不久就升级系统了,发现又忘记了步骤,虽然网上有很多教程,但寻找需要浪费太多的时间,由于github连接比较慢,所以使用了开源中国的托管http://git.oschina.net ...

  6. Dockerfile 常用指令 - 每天5分钟玩转 Docker 容器技术(16)

    是时候系统学习 Dockerfile 了.下面列出了 Dockerfile 中最常用的指令,完整列表和说明可参看官方文档. FROM指定 base 镜像. MAINTAINER设置镜像的作者,可以是任 ...

  7. 【设计模式】单一职责原则(SRP)

    单一职责原则是面向对象原则五大原则中最简单,也是最重要的一个原则, 他的字面定义如下: 单一职责原则(Single Responsibility Principle, SRP): 一个类只负责一个功能 ...

  8. WINFORM实现进程信息的查看,listview,点击,右键,右键菜单

    1.   程序设计要求 设计程序来获取计算机中的进程信息 2.程序设计流程图 3.程序设计亮点 (0)程序启动时加载guide引导使用视频 (1)使用Listview控件显示出所有控件的名称. (2) ...

  9. 白话讲session

    什么是session Session一般译作会话,牛津词典对其的解释是进行某活动连续的一段时间.从不同的层面看待session,它有着类似但不全然相同的含义.比如,在web应用的用户看来,他打开浏览器 ...

  10. SSM框架中常用的注解

    @Controller:在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model , ...