关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制。

  

  一旦某个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

  1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

  Java 内存模型规定了所有的变量都存储在主内存(Main Memory)中(仅是虚拟机内存的一部分)。每条线程还有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

  某线程在对自己工作内存中的某变量进行写操作时,如果发现该变量是一个使用volatile修饰的变量,那么它会将该变量的值更新到主内存中,此举会导致其他线程各自工作内存中保存的副本变量失效。而其他线程在读取各自工作内存中的副本变量时,发现副本变量已失效,就会转向去主内存中读取变量。这就使得所有线程读取到的该变量的值永远是最新的,保证了该变量的可见性。 

  2.禁止进行指令重排序,从而保证了指令执行的有序性。

  该点语义,最常见的使用场景便是单例模式。

package com.huang.pims.demo.patterns.singleton;

public class VolatileSingleton {

    private static volatile VolatileSingleton singletonInstance;

    private VolatileSingleton() {
super();
} public static VolatileSingleton getInstance() {
if (null == singletonInstance) {
synchronized (SyncCodeSingleton.class) {
if (null == singletonInstance) {
singletonInstance = new VolatileSingleton();
}
}
}
return singletonInstance;
} }

  其中,singletonInstance = new VolatileSingleton();在代码层面,这只是一行代码,但是JVM在执行改行代码的时候,会分三步执行

  1.   分配内存
  2.   使用VolatileSingleton的构造函数来初始化实例对象
  3.   将singletonInstance指向分配的内存空间(执行完这步 singletonInstance 就不再为 null 了)

  在不使用volatile关键字修饰singletonInstance的前提下,JVM为了提高自身的效率,可能会进行指令重排序,互换第二步和第三步的执行顺序。

  假设线程A运行到singletonInstance = new VolatileSingleton();时,刚执行完第一步和第三步,还没来得及初始化实例对象,而此时的singletonInstance已然不是null了。这时,如果有线程B运行到首次判空的地方,判断结果自然为false,那么线程B就会返回一个没有初始化的singletonInstance,从而导致获取单例对象失败。

  使用volatile关键字修饰singletonInstance,禁止了指令重排序,就可以保证,如果singletonInstance不为null,singletonInstance必然已经经过了初始化。

volatile不能确保原子性
public class Test {

  public volatile int inc = 0; public void increase() {
inc++;
} public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
} while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}

  在上述代码中,多个线程并发执行increase()方法。increase()方法中的自增操作(inc++;)可以分为三步:读取inc变量值,inc变量值加一,更新工作内存和主内存中的inc变量值。然而Java的自增操作并不具备原子性,就有可能产生以下的情景:

  线程A运行到inc++;处,刚读取到inc变量的值(这必然是最新的值),还没来的及后续操作,cpu分配的时间片就用完了,切换到线程B运行inc++;线程B成功更新了inc变量的值,并将inc变量的值更新到了主内存中。当再次轮到线程A执行的时候,接着之前的操作继续执行,由于之前已经读取了inc变量的值,就不会再去读取,这就导致该线程中的inc变量值不再是最新的值。最后导致旧值+1,覆盖了最新值。

  解决方案:可以通过synchronized或lock,进行加锁,来保证操作的原子性。也可以通过AtomicInteger。

Java关键字-volatile的更多相关文章

  1. JAVA关键字Volatile的特性

    一.简述: 关键字Volatile是JAVA虚拟机提供的最轻量级的同步机制,但是它并不容易完全被正确.完整的理解,以致于许多程序员在遇到需要处理多线程数据竞争的时候一律使用synchronized来进 ...

  2. Java 关键字volatile的解释

    volatile 关键字特征: 1.可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.可以禁止线程的工作内存对volatile修饰的变量进行缓存,并将修改的变量立即写入主存. 2. ...

  3. Java内存模型及Java关键字 volatile的作用和使用说明

    先来看看这个关键字是什么意思:volatile  [ˈvɒlətaɪl] adj. 易变的,不稳定的; 从翻译上来看,volatile表示这个关键字是极易发生改变的.volatile是java语言中, ...

  4. 关于 Java 关键字 volatile 的总结

    1 什么是 volatile volatile 是 Java 的一个关键字,它提供了一种轻量级的同步机制.相比于重量级锁 synchronized,volatile 更为轻量级,因为它不会引起线程上下 ...

  5. java 关键字volatile

    一.Java内存模型 想要理解volatile为什么能确保可见性,就要先理解Java中的内存模型是什么样的. Java内存模型规定了所有的变量都存储在主内存中.每条线程中还有自己的工作内存,线程的工作 ...

  6. java关键字volatile用法详解

    volatile关键字想必大家都不陌生,在java 5之前有着挺大的争议,在java 5之后才逐渐被大家接受,同时作为java的关键字之一,其作用自然是不可小觑的,要知道它是java.util.con ...

  7. 深入汇编指令理解Java关键字volatile

    volatile是什么 volatile关键字是Java提供的一种轻量级同步机制.它能够保证可见性和有序性,但是不能保证原子性 可见性 对于volatile的可见性,先看看这段代码的执行 flag默认 ...

  8. Java 关键字volatile 与 synchronized 作用与区别

     1,volatile 它所修饰的变量不保留拷贝,直接访问主内存中的.    在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器).为了性能,一个线程会在自己 ...

  9. Java关键字volatile的实现原理(四)

    简述 volatile 是轻量级的synchronized,在多线程开发中保证了共享变量的可见性.可见性就是当一个线程修改一个共享变量时,另一个线程可以读到修改的值.如果volatile变量使用恰当, ...

随机推荐

  1. eclipse输入提示 设置

  2. sed替换变量

    今天在写脚本时用到了sed,我用sed替换xml文件中的变量.一般在sed 中替换都用单引号,如下边 sed -i ‘s/10/1000/g’ test.xml但是如果需要把1000改成变量,如sed ...

  3. UISwitch用法:

    代码: #import "ViewController.h" @interface ViewController () @end @implementation ViewContr ...

  4. Android「后台下载」Feb.24小记

    参考了CSDN上的这个文章(HERE),之前只是新开一个线程: public class DownloadThread implements Runnable{ String tarFile ; pu ...

  5. EOJ Monthly 2018.4 (E.小迷妹在哪儿(贪心&排序&背包)

    ultmaster 男神和小迷妹们玩起了捉迷藏的游戏. 小迷妹们都希望自己被 ultmaster 男神发现,因此她们都把自己位置告诉了 ultmaster 男神,因此 ultmaster 男神知道了自 ...

  6. [Selenium] 使用自定义的FirefoxProfile

    FirefoxProfile 用于定制待测试的Firefox 浏览器的特定属性,其中包括所存储的密码.书签.历史信息.Cookies等.某些测试用例需要用到特定的用户信息,因此可通过定制当前Firef ...

  7. SQL查询速度

    查询速度 https://www.cnblogs.com/ZaraNet/p/9558272.html 影响你的查询速度的原因是什么? 网速不给力,不稳定. 服务器内存不够,或者SQL 被分配的内存不 ...

  8. 「LuoguP2014」 选课

    Description 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程来学习,在课程里有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习.现在有N门功课,每门课有个学 ...

  9. BZOJ3990 排序

    题目:www.lydsy.com/JudgeOnline/problem.php?id=3990 这题很不错. 刚开始时无从下手,想了好多$O((2^n)log(2^n))$ 的idea,但是都不行. ...

  10. Python 函数的参数传递

    C/C++中,传递参数的类型是可以指定的.一般来说,传递参数可以分为两种:值传递和引用传递.对于值传递,参数传递的过程中进行了复制操作,也就是说,在函数中对参数的任何改动都不会影响到传入的变量:对于引 ...