java(十三):volatile与内存模型
来源:互联网 发布:datagard 数据同步 编辑:程序博客网 时间:2024/06/01 10:29
作为深入理解java中的锁,首先应该掌握volatile的含义和用法。
线程之间的可见性
可见性对于java初学者并不是一个从字面上就可以简单理解的名词。
往深了说,要真正掌握volatile关键字,还需要有基本的jvm的知识,这里只需要理解jvm的分区以及各个区的内容含义就知道了,参考前面的一篇博客jvm内存划分
我们需要掌握的有两点:
- 同一进程的不同线程之间其内存空间是不共享的,类似虚拟机栈上的局部变量表
- 除了jvm使用的内存空间,CPU上的寄存器等也可以保存变量(称为CPU缓存,即Cache)
因此,尤其注意在多线程共享一个变量时,需要使用volatile修饰,如下:
volatile boolean flag;//在线程A执行public void shutdown(){ flag = true;}//在线程B执行public void doWork(){ while(!flag){ //do work }}
从上述代码中可以得出,当A线程调用后,B线程的函数会立刻结束,因为A线程对flag的改变被直接写入内存,而不是cpu cache,这会导致其他线程重新从内存读取flag变量。而如果不加volatile,那么B线程的循环可能会过一段时间才结束。
指令重排
一旦涉及到多线程,就会出现许多我们无法预测的问题,比如指令重排。
普通的变量会保证以下的执行:
在该方法内,所有依赖赋值结果的地方都能获取到正确的结果,单不能保证变量赋值的顺序和代码中完全一致
比如如下一段代码
//共享变量int a = 0;int b = 1;volatile boolean flag = false;//A线程执行public void change(){ a = 100; b = a; flag = true;}//B线程执行public void doWork(){ while(!flag){ sleep(); } System.out.println(b);}
在上述代码中,如果不加volatile,可能会出现B输出1。这就是指令重排带来的副作用。
我们前面说过,对于函数内部,即change()函数,肯定会保证a = 100;b = a;
的执行顺序,因为存在变量依赖,如果不加volatile,实际上却并不会保证flag = true;
在函数change()最后执行,这就是指令重排,即jvm内部对其只进行指令优化后导致语句的执行顺序改变。
于是在多线程,就出现问题了,比如在上述的B线程中,如果没有volatile,则B可能输出1,因为A线程中限制性flag=true,b的值还没来得及更新,B线程就输出b了。
happens-before
先行发生,对于前面的指令重排,并不是所有情况都会存在,java语言内部有一个happens-before原则,它可以判断一个变量的改变在多个线程中是否是可见的。
如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。
java内存模型中存在一些天然的happens-before关系,但也有些关系不在此中(比如前面的变量赋值),而jvm可以对这些不在此列的进行重排序。
volatile
然后我们来看看volatile关键字,volatile共两个作用:
命令CPU不使用CPU Cache.
即每次获取变量时,都从内存中去取,以保证变量的最新值,以此来达到变量对于所有线程都是可见的。禁止该变量的指令重排
实际上volatile的作用就到这里为止了,我们发现volatile并没有实现同步的机制,因此volatile是很轻量级的。一般来说volatile和锁或者synchronized一起使用,已达到同步的目的。
总的来说,volatile变量读操作的性能和普通变量几乎没有什么差别,但是写操作可能会慢一点。
单例举例
volatile最常用的就是计数和单例模式。
下面的例子将说明我们平时写的单例模式是有问题的。
private static class Singleton{ private static Singleton instance; public Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; }}
上述代码是最常见的创建单例的例子,就实际效果来说,也没太大问题,但问题在于上述的单例模模式并不“单例”,实际上有可能创建多个Singleton对象,问题在于,如果我们承认变量的不可见性,那么如果多个线程调用getInstance(),那么由于不可见性,即使其中一个线程创建了新对象,也存在其他线程没有发现而又创建一次的情况。
最佳的单例模式应该如下:
private static class Singleton{ private volatile static Singleton instance; public Singleton(){} public static Singleton getInstance(){ if(instance == null){ synchronized (Singleton.class){ if(instance == null){ instance = new Singleton(); } } } return instance; }}
参考博客:Java中Volatile关键字详解,happens-before
- java(十三):volatile与内存模型
- Java内存模型与volatile
- java内存模型与volatile
- Java内存模型与volatile
- volatile与Java内存模型
- Java内存模型与volatile
- 【Java多线程】Java内存模型与Volatile
- Java内存模型与volatile关键字
- Java内存模型与volatile关键字
- Java内存模型与volatile关键字
- java内存模型:volatile变量、与synchronized
- volatile关键字与Java同步内存模型
- Java内存模型与volatile关键字
- java内存模型与volatile关键字
- Java 内存模型 & volatile
- 【Java】内存模型 volatile
- Java内存模型(三)-Volatile
- Java内存模型&volatile关键字
- Swift 之父 Chris Lattner 访谈录(超长完整版)
- 15算法课程 111. Minimum Depth of Binary Tree
- MVC、MVP、和MVVM
- 插入排序的变形
- web开发中常用css兼容代码(包括移动端)
- java(十三):volatile与内存模型
- HBase java client配置参数
- Python 核心编程习题2
- 页面动态删除js、css文件
- java关系视图
- 树莓派3B+ 挂载储存设备
- C# 多線程的應用
- Android Studio快捷键收集(Eclipse版)
- BZOJ 4710 分特产 容斥原理