使用volatile和不使用volatile的区别在于JVM内存主存和线程工作内存的同步之上。volatile保证变量在线程工作内存和主存之间一致。以下是示例程序,成员变量boolValue用volatile修饰会导致程序很快退出:
使用volatile和不使用volatile的区别在于JVM内存主存和线程工作内存的同步之上。volatile保证变量在线程工作内存和主存之间一致。以下是示例程序,成员变量boolValue用volatile修饰会导致程序很快退出: Java代码 package linyumin.test.thread; /** * * @author llade * */ public class VolatileObjectTest { /** * 成员变量boolValue使用volatile和不使用volatile会有明显区别的。 * 本程序需要多试几次,就能知道两者之间的区别的。 * @param args */ public static void main(String[] args) { final VolatileObjectTest volObj= new VolatileObjectTest(); Thread t1=new Thread(){ public void run(){ System.out.println("t1 start" ); for (;;){ volObj.waitToExit(); } } }; t1.start(); Thread t2=new Thread(){ public void run(){ System.out.println("t2 start" ); for (;;){ volObj.swap(); } } }; t2.start(); } boolean boolValue; //加上volatile 修饰的是时候,程序会很快退出,因为volatile 保证各个线程工作内存的变量值和主存一致。所以boolValue == !boolValue就成为了可能。 public void waitToExit() { if (boolValue == !boolValue)System.exit( 0 ); //非原子操作,理论上应该很快会被打断。实际不是,因为此时的boolValue在线程自己内部的工作内存的拷贝,因为它不会强制和主存区域同步,线程2修改了boolValue很少有机会传递到线程一的工作内存中。所以照成了假的“原子现象”。 } public void swap() { //不断反复修改boolValue,以期打断线程t1. boolValue = !boolValue; } } 首先每个线程都有自己一个工作内存区,多个线程共享一个主内存区。线程中的本地变量存在自己的内存区中,如for(int i=0;i <100;i++){this.i=i;},其中i就存在线程工作内存中,即每个线程都有一个,不用也不能加volatile关键字,this.i就是共享变量。而共享的变量就存在主内存区里,但Java线程为了提高效率,会把共享变量拷贝到自己的工作区中,这就产生了变量一致性的问题。 java提供的一种方法是互斥访问,互斥访问会在加锁和解锁中维持变量的一致性,另一种就是volatile关键字。
java language specification中的一个例子,有类如下: class Test { static int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println( "i= " + i + " j= " + j); } } 有两个线程,一个不停调用 one(),一个不停调用 two(),则有可能出现这种情况,打印出来的j比i还大。因为这时线程对共享变量的更新是无序的。 1.使用同步方法: class Test { static int i = 0, j = 0; static synchronized void one() { i++; j++; } static synchronized void two() { System.out.println( "i= " + i + " j= " + j); } } 这就不用我介绍了,i和j始终一样大。 2.使用volatile class Test { static volatile int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println( "i= " + i + " j= " + j); } } 这样能允许one()和two()并发执行,同时使one()如字面一样执行。这一般能使打印出来的j不会大于i,在更新j之前会先更新i。但有可能打印出来的j比i大很多,因为one()可能在two获取i和j之间执行了很多次。
说明一下: java language specification中的例子不好,有点晦涩,但我认为volatile的作用就是保证任何时候主内存的i都大于等于j。two()中出现j比i大很多,只是因为它访问的是不同时刻的主内存。 还有一点,就是volatile能防止编译器对变量进行优化,每次共享变量都到主内存。如果线程不到主内存中读,变量的值就会不正确。 强制线程到主存中读,应该也包含上面wulemale所说的,变量被程序外部改变的情况。 |