一、前言

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

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

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

二、主程序

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);
}
}

三、单例模式测试

  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。

四、关于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 的例子,我实在是写不出来。

java中volatile关键字的更多相关文章

  1. 【转】java中volatile关键字的含义

    java中volatile关键字的含义   在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...

  2. 转:java中volatile关键字的含义

    转:java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...

  3. Java中Volatile关键字详解 (转自郑州的文武)

    java中volatile关键字的含义:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 一.基本概念 先补充一下概念:J ...

  4. Java中Volatile关键字详解

    一.基本概念 先补充一下概念:Java并发中的可见性与原子性 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值, ...

  5. java中volatile关键字的理解

    一.基本概念 Java 内存模型中的可见性.原子性和有序性.可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有 ...

  6. Java中Volatile关键字详解(转载)

    转载自:https://www.cnblogs.com/zhengbin/p/5654805.html 一.基本概念 先补充一下概念:Java 内存模型中的可见性.原子性和有序性. 可见性: 可见性是 ...

  7. java中volatile关键字的含义

    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语 ...

  8. java中volatile关键字的含义 (转载)

    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语 ...

  9. 深入解析Java中volatile关键字的作用

    转(http://m.jb51.net/article/41185.htm)Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制 在java线 ...

  10. 关于Java中volatile关键字笔记

    volatile通常被认为是一种轻量级的synchronized,字面上它表示易变的,在并发编程中,它保证了共享变量的可见性.所谓可见性指的是,某个线程对变量进行操作后,其他线程能够读取到操作后的最新 ...

随机推荐

  1. 我的前端故事----疯狂倒计时(requestAnimationFrame)

    很久没有更新博客了...为了双十一准备了不少活动,终于结束了,有时间静静的坐下来总结一下了,在活动中最常用的就是倒计时了,晚上也有很多倒计时的例子了,那么今天带来的是一个新的方法和思路. 既然要介绍新 ...

  2. Java 应该跨四个平台

    编程语言从属于操作系统,要统一,就要在根本处统一,要统一的是操作系统,而不是编程语言.你认为是苹果决定苹果树,还是苹果树决定苹果? 编程语言跨操作系统是错误的道路,你见过苹果长在桔子树上的吗?苹果长得 ...

  3. ES6中的const命令【转】

    1.const声明一个只读常量,一旦声明,常量的值就不能改变 1 const PI=3.1415; 2 console.log(PI);//3.1415 3 4 PI=3;//Uncaught Typ ...

  4. 从源代码构建 Go 开发环境

    从源代码构建 Go 开发环境 Go 1.5 之前的版本 安装C 语言开发环境 在Go 1.5 之前的版本(比如 1.3.1.4),都会部分的依赖 C 语言的工具链,所以如果你有C 语言的开发环境,就可 ...

  5. Web服务网站故障分析常用的命令

    1.查看TCP连接状态netstat -nat |awk ‘{print $6}’|sort|uniq -c|sort -rn netstat -n | awk ‘/^tcp/ {++S[$NF]}; ...

  6. Python数据分析

    一.安装Anaconda 1.下载:https://www.continuum.io/downloads 2.命令行创建和启动环境 conda create --name py35 python=3. ...

  7. tornado web 框架的认识

    tornado 简介 1,概述 Tornado就是我们在 FriendFeed 的 Web 服务器及其常用工具的开源版本.Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的 ...

  8. 数据库(SQL Server)管理数据库表~新奇之处

    说到“数据库”,我总有一种莫名的感觉,在刚刚接触到的数据库中就让我似懂非懂渡过着,于是思考着.于是在冷静的时空中让我回想到了很多的知识,不知你们是怎样过来的,真心希望我的这篇数据库总结能够让我们都有一 ...

  9. js 预处理用户上传图片

    前几个月闲得无聊写得一段代码,没想最近刚好用上了,在硬盘里翻了半天找回来,还没好好整理直接用上了手机用户拍照上传的图片一般都在1M 到4M 之间,如果只是用作头像尺寸就可以缩小很多,1M甚至几M的图转 ...

  10. 多位数每一位个系数:个位num%10;十位num/10%10.......

    请输出满足这样条件的五位数. 个位=万位 十位=千位 个位+十位+千位+万位=百位 思路: 1.定义一个要操作的五位数变量num 2.求出每一位个系数 个:num%10 十:num/10%10 百:n ...