首页 > 代码库 > Java Volatile关键字

Java Volatile关键字

在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。

这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。 

要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。 

Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。

这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

Code:

 

public class Counter {    public  static int count = 0;    public static void inc() {        //这里延迟1毫秒,使得结果明显        try {            Thread.sleep(1);        } catch (InterruptedException e) {        }        count++;    }    public static void main(String[] args) {        //同时启动1000个线程,去进行i++计算,看看实际结果        for (int i = 0; i < 1000; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    Counter.inc();                }            }).start();        }        //这里每次运行的值都有可能不同,可能为1000        System.out.println("运行结果:Counter.count=" + Counter.count);    }}
技术分享
运行结果:Counter.count=980
View Code

添加Volatile

public class Counter {    public volatile static int count = 0;    public static void inc() {        //这里延迟1毫秒,使得结果明显        try {            Thread.sleep(1);        } catch (InterruptedException e) {        }        count++;    }    public static void main(String[] args) {        //同时启动1000个线程,去进行i++计算,看看实际结果        for (int i = 0; i < 1000; i++) {            new Thread(new Runnable() {                @Override                public void run() {                    Counter.inc();                }            }).start();        }        //这里每次运行的值都有可能不同,可能为1000        System.out.println("运行结果:Counter.count=" + Counter.count);    }}

Result:

技术分享
运行结果:Counter.count=801
View Code

运行结果还是没有我们期望的1000,下面我们分析一下原因

 

每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存

变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。

 技术分享

 

但是这一些操作并不是原子性,也就是 在read load之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样

对于volatile修饰的变量,jvm虚拟机只是保证从主内存加载到线程工作内存的值是最新的

例如假如线程1,线程2 在进行read,load 操作中,发现主内存中count的值都是5,那么都会加载这个最新的值

在线程1堆count进行修改之后,会write到主内存中,主内存中的count变量就会变为6

线程2由于已经进行read,load操作,在进行运算之后,也会更新主内存count的变量值为6

导致两个线程及时用volatile关键字修改之后,还是会存在并发的情况。

技术分享
Volatile和Staticstatic指的是类的静态成员,实例间共享volatile跟Java的内存模型有关,线程执行时会将变量从主内存加载到线程工作内存,建立一个副本,在某个时刻写回。valatile指的每次都读取主内存的值,有更新则立即写回主内存。理解了这两点,逐句再来解释你的困惑:“既然static保证了唯一性”:static保证唯一性,指的是static修饰的静态成员变量是唯一的,多个实例共享这唯一一个成员。“那么他对多个线程来说都是可见的啊”:这里,static其实跟线程没太大关系,应该说对多个对象实例是可见的。你说对多个线程可见,虽然没什么毛病,因为静态变量全局可见嘛,但是把这个理解转到线程的上线文中是困惑的起因。“volatile保证了线程之间的可见性”:因为线程看到volatile变量会去读取主内存最新的值,而不是自个一直在那跟内部的变量副本玩,所以保证了valatile变量在各个线程间的可见性。“那么修改的时候只要是原子操作,那么就会保证它的唯一性了吧”:此时你说的“唯一性”,指的是各个线程都能读取到唯一的最新的主内存变量,消除了线程工作内存加载变量副本可能带来的线程之间的“不唯一性”。这里“唯一性”的含义跟第一句说的“唯一性”是不一样的。“这两个在我理解上我觉得差不多。”:其实解决问题的“场景”是完全不一样的。造成理解困惑最大的原因在于,这两个场景略有类似,以致混淆了:场景1:各个类的实例共享唯一一个类静态变量场景2:各个线程共同读取唯一的最新的主内存变量的值
View Code

synchronized开销比volatile大,volatile能够胜任的用volatile。

volatile不保证原子操作,所以,很容易读到脏数据。

http://www.ibm.com/developerworks/cn/java/j-jtp06197.html

http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html

http://blog.csdn.net/feier7501/article/details/20001083

https://www.zhihu.com/question/41579791

Java Volatile关键字