ava中volatile关键字

简介:

v一、前言

  JMM提供了volatile变量定义、final、synchronized块来保证可见性。

  用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。写了几个测试的例子,大家可以试一试。

  关于JMM,可参考:http://www.cnblogs.com/hujunzheng/p/5118256.html

v二、主程序

复制代码
public class Main{
    public static void main(String[] args) throws InterruptedException{
        List<Thread> threadList = new ArrayList<Thread>();
        for(int i=0; i<10; ++i){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    Single.Holder.instance.add();
                }
            });
            threadList.add(thread);
            thread.start();
        }
        
        for(Thread thread : threadList)
            thread.join();
        
        System.out.println(Single.Holder.instance.x);
    }
}
复制代码

v三、单例模式测试

  1、没有volatile,没有synchronized的情况   

复制代码
class Single{
    public int x = 0;
    public void add(){
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ++this.x;
    }
    
    public static class Holder{
        public static Single instance = new Single();
    }
}
复制代码

    输出结果:8, 9, 10都出现过。可以多运行,多试一试,就会发现不同的结果。

  2、有volatile,没有synchronized

复制代码
class Single{
    public volatile int x = 0;
    public void add(){
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ++this.x;
    }
    
    public static class Holder{
        public static Single instance = new Single();
    }
}
复制代码

    输出结果:最多出现的是9 和 10。

  3、没有volatile,有synchronized

复制代码
class Single{
    public int x = 0;
    public synchronized void add(){
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ++this.x;
    }
    
    public static class Holder{
        public static Single instance = new Single();
    }
}
复制代码

  输出结果:无论运行多少次都是10。

v四、关于volatile在DCL(double check lock)中的应用

  摘自:http://www.iteye.com/topic/260515 ,讲的不错。

复制代码
public class LazySingleton {
    private int someField;
    
    private static LazySingleton instance;
    
    private LazySingleton() {
        this.someField = new Random().nextInt(200)+1;         // (1)
    }
    
    public static LazySingleton getInstance() {
        if (instance == null) {                               // (2)
            synchronized(LazySingleton.class) {               // (3)
                if (instance == null) {                       // (4)
                    instance = new LazySingleton();           // (5)
                }
            }
        }
        return instance;                                      // (6)
    }
    
    public int getSomeField() {
        return this.someField;                                // (7)
    }
}
复制代码

  首先说明一下,为什么这种写法在java中是行不通的!

  假设线程Ⅰ是初次调用getInstance()方法,紧接着线程Ⅱ也调用了getInstance()方法和getSomeField()方法,我们要说明的是线程Ⅰ的语句(1)并不happen-before线程Ⅱ的语句(7)。线程Ⅱ在执行getInstance()方法的语句(2)时,由于对instance的访问并没有处于同步块中,因此线程Ⅱ可能观察到也可能观察不到线程Ⅰ在语句(5)时对instance的写入,也就是说instance的值可能为空也可能为非空。我们先假设instance的值非空,也就观察到了线程Ⅰ对instance的写入,这时线程Ⅱ就会执行语句(6)直接返回这个instance的值,然后对这个instance调用getSomeField()方法,该方法也是在没有任何同步情况被调用,因此整个线程Ⅱ的操作都是在没有同步的情况下调用 ,这说明线程Ⅰ的语句(1)和线程Ⅱ的语句(7)之间并不存在happen-before关系,这就意味着线程Ⅱ在执行语句(7)完全有可能观测不到线程Ⅰ在语句(1)处对someFiled写入的值,这就是DCL的问题所在。很荒谬,是吧?DCL原本是为了逃避同步,它达到了这个目的,也正是因为如此,它最终受到惩罚,这样的程序存在严重的bug,虽然这种bug被发现的概率绝对比中彩票的概率还要低得多,而且是转瞬即逝,更可怕的是,即使发生了你也不会想到是DCL所引起的。

  我的理解是:线程I 和线程II 都有自己的工作存储,线程I 创建好了instance后,向内存刷新的时间是不确定的,所以线程Ⅱ在执行语句(7)完全有可能观测不到线程Ⅰ在语句(1)处对someFiled写入的值。

  那么由于在java 5中多增加了一条happen-before规则:

  • 对volatile字段的写操作happen-before后续的对同一个字段的读操作。

  利用这条规则我们可以将instance声明为volatile,即: private volatile static LazySingleton instance; 

    根据这条规则,我们可以得到,线程Ⅰ的语句(5) -> 语线程Ⅱ的句(2) (也就是线程),根据单线程规则,线程Ⅰ的语句(1) -> 线程Ⅰ的语句(5)和语线程Ⅱ的句(2) -> 语线程Ⅱ的句(7),再根据传递规则就有线程Ⅰ的语句(1) -> 语线程Ⅱ的句(7),这表示线程Ⅱ能够观察到线程Ⅰ在语句(1)时对someFiled的写入值,程序能够得到正确的行为。

  补充:在java5之前对final字段的同步语义和其它变量没有什么区别,在java5中,final变量一旦在构造函数中设置完成(前提是在构造函数中没有泄露this引用),其它线程必定会看到在构造函数中设置的值。而DCL的问题正好在于看到对象的成员变量的默认值,因此我们可以将LazySingleton的someField变量设置成final,这样在java5中就能够正确运行了。

 

  就先写到这里了,如果大神路过,可以给我提供一个测试 volatile 的例子,我实在是写不出来。 










本文转自 小眼儿 博客园博客,原文链接:http://www.cnblogs.com/hujunzheng/p/5335994.html,如需转载请自行联系原作者
目录
相关文章
|
1月前
|
缓存 编译器
volatile关键字
volatile关键字
|
3月前
|
缓存 编译器 C语言
一起来探讨volatile关键字
在C语言中,volatile是一个关键字,用于告诉编译器不要对被声明为volatile的变量做优化,以确保每次对该变量的读写都直接操作内存。
|
6月前
|
缓存 安全 Java
【volatile关键字】
【volatile关键字】
|
2月前
|
存储 安全 Java
[Java]volatile关键字
[Java]volatile关键字
30 0
|
4月前
|
SQL 缓存 安全
java中volatile关键字
java中volatile关键字
37 0
java中volatile关键字
|
9月前
|
安全 Java
Java中的Volatile关键字
Java中的Volatile关键字
|
8月前
|
存储 缓存 Java
volatile 关键字 (详细解析)
volatile 关键字 的工作原理: 1、子线程t 和 main方法从主内存读取到数据放入其对应的工作内存,此时 flag的值为false 2、子线程t 将flag的值更改为true 3、在某一时刻 子线程t将flag的值写回主内存后,失效其他线程对此变量副本 4、main方法 再次对flag进行操作的时候线程会从主内存读取最新的值,放入到工作内存中
131 0
|
存储 缓存 Java
volatile关键字解析
volatile关键字解析
142 0
volatile关键字解析
|
存储 缓存 Java
Java中volatile关键字
Java中volatile关键字
157 0
Java中volatile关键字
|
缓存 Java
java中的Volatile关键字使用
java中的Volatile关键字使用