浅谈volatile关键字
volatile是一种轻量级的同步机制。它可以保证内存可见性以及防止指令重排序,但是不保证原子性
volatile和JMM机制是不可分割的,在谈volatile的时候有必要先了解以下JMM
JMM(Java内存模型)
JMM是一种抽象的概念模型,实际上并不存在。JMM主要可以将内存看成两块,一块是主内存,用于存放共享变量,线程间共享。
一块是线程私有的工作内存,存放变量副本。每次线程生成的时候都会创建一个私有的工作内存。当线程要操作主内存中的共享
变量的时候需要先将变量复制到自己的工作内存中,在工作内存中对变量副本进行操作,操作完成后再同步回主内存。

简单了解了JMM后我们就深入了解一下volatile是如何保证内存可见性,禁止指令重排序,又是为什么不保证原子性
内存可见性
由JMM模型我们可以看到,每个线程都是再各自的工作内存中进行工作,它们只知道自己把变量改成什么样了,并不知道其他线程把
变量改成什么样子了。这样会出现一种问题:假设主内存中有变量a的值为0,线程A读取变量a并且copy一份副本到自己的工作内存,
线程B也读取变量a且cope一份副本到自己的工作内存,线程A给变量a加上10,线程B给变量a加上20。那么我们期望的结果是最终主
内存中的变量a的值被同步成了30.但是由于线程A和线程B并不知道对方所作的修改,必定有一方将自己的变量副本同步进主内存的时
侯覆盖掉了另外一放的结果,主内存中变量a的值只会是10或者20。如下图所示。

内存可见性就是每个线程可以感知到其他线程对该变量所做的修改,操作该变量时都会从主内存中取最新的值。还是拿上图的例子来说,
假设线程A对工作内存中的变量a操作完并且通过回主内存后,线程B立马感知该变化,然后从主内存中取出最新的变量a的值,即10,然后对
10加上20然后同步回主内存,那么最终结果就正确了。内存可见性就是一个线程对共享变量做出改变后其他线程能够立即感知该变化,并且从
主内存中获取最新值,然后进行操作。
不保证原子性
那么volatile每次都是从主内存中获取最新的值进行操作为什么不保证原子性呢,每次都获取最新的值去操作那么结果不就肯定正确的吗。其实不然,
在这里我们要明确一个概念,每次线程在对工作内存中的变量副本操作完后要同步回主内存的时候,一时只能有一个线程同步,如果有多个线程要
往主内存中同步,也只有一个会成功,其他线程会被阻塞在外面,并且挂起。是不是很像对主内存上了一把锁呢。
对于i++这种操作,其实底层是被分成了三个指令来执行。
1 从主内存中拿到值到工作内存
2 对i进行累加
3 将累加后的值写入主内存
考虑这么一种情况,线程A和线程B同时对副本变量操作完了,并且都要同步回主内存,这时候只有一个线程能够通过成功,假设线程A成功获得了主
内存的操作权,线程B就被挂起,当线程A同步完毕后,我们都知道cpu速度是超级快的,这时线程B还没被通知到该变量已被更新时线程B就将变量
同步到主内存并且覆盖掉线程A的修改了。因此volatile不保证原子性。
要想保证原子性可以对累加操作上锁,或者使用atomic原子类
防止指令重排序
我们编写的代码都是被编译成字节码文件然后再去执行的,为了加快程序运行,编译器对指令进行了重排序,程序执行的顺序并不是和我们代码写的顺
序是一样的。比如 int a = 10,b = 20;我们期望的是a先赋值,b再赋值,但是最终执行的时候可能因为指令从排序导致了b先赋值,a后赋值。指令重排序
的前提是数据间不存在数据依赖性。在单线程环境中,不管指令如何重排序,编译器都会保证最后执行结果的正确性。但是在多线程的情况下,可能会出现
各个程序乱序执行,各个线程数据产生了不一致性,运行结果出错等问题。volatile通过加内存屏障进行指令重排序
内存可见性代码验证
/**
* 可见性验证
* @author chen
*
*/ class MyDate{
//没有加volatile,线程A对date的修改没有通知到其他线程,主线程陷入死循环
//private int date = 0; //加了volatile,线程A对date的修改通知到其他线程,主线程会更新自己的变量副本为最新值,不会陷入死循环
private volatile int date = 0; public void setDate(int date) {
this.date = date;
} public int getDate() {
return date;
}
} class MyThread implements Runnable{ private MyDate myDate; public void setMyDate(MyDate myDate) {
this.myDate = myDate;
} @Override
public void run() {
System.out.println(Thread.currentThread().getName()+"启动了。。。");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myDate.setDate(10);
System.out.println(Thread.currentThread().getName()+"结束了,date数据为:"+myDate.getDate());
}
} public class KeJianXingTest { public static void main(String[] args) {
MyDate date = new MyDate(); MyThread myThread = new MyThread();
myThread.setMyDate(date);
new Thread(myThread,"线程A").start(); while(date.getDate()==0) {
//如果一直死循环就说明线程A对date的修改没有通知到主线程,及主线程工作
//空间持有的变量副本还是date = 0
} System.out.println("主线程结束,date数据为:" + date.getDate());
}
}
不保证原子性代码验证
/**
* 验证不保证原子性
* @author chen
*
*/
class MyDate2{
volatile int num = 0; //AtomicInteger atomicInteger = new AtomicInteger();
} public class YuanZiXingTest { public static void main(String[] args) {
MyDate2 myDate2 = new MyDate2(); for(int i = 1;i<=10;i++) {//10个线程,每个线程对num+1000,结果应该为10000
new Thread(()->{
for(int j = 0;j<1000;j++) {
myDate2.num++;
//myDate2.atomicInteger.getAndIncrement();
}
},"线程" + i).start();
} while(Thread.activeCount()>2) {//保证10个线程都执行完毕
Thread.yield();
}
System.out.println(myDate2.num);//结果<=10000
}
}
浅谈volatile关键字的更多相关文章
- 浅谈Volatile与多线程
标题:浅谈Volatile与多线程 2011-04-19 22:49:17 最近看的比较杂,摘了一些人的笔记!随着多核的日益普及,越来越多的程序将通过多线程并行化的方式来提升性能.然而,编写正 ...
- 浅谈Static关键字
1.使用static关键字声明的属性为全局属性 未使用static关键字指定city之前,如果需要将Tom,Jack,Mary三人的城市均改成Beijing,需要再次声明三次对象的city为Beiji ...
- 浅谈Dynamic 关键字系列之一:dynamic 就是Object(转)
C# 4.0提供了一个dynamic 关键字,那么什么是dynamic,究竟dynamic是如何工作的呢? 从最简单的示例开始: static void Main(string[] args) { d ...
- 浅谈 var 关键字
提起 var关键子,程序员的第一反应就是JavaScript, 事实上这个关键子在其他语言中也有被采用. 比如说C#, 比如说kotlin, 用法和JavaScript中使用差不多,作为要声明变量的前 ...
- 浅谈this关键字
在我学习this关键字的时候,通过查找资料总结出一些this的特殊用法, 供大家参考,代码里面有我总结的分析过程! 箭头函数里的this: var username = "全局"; ...
- 浅谈final关键字的用法
1.final变量: 常和static一起使用,修饰成员变量或者本地变量.修饰后为常量,不可以再次初始化(再次引用),例如public static final String SUCCESS= &qu ...
- 浅谈javascript-this关键字
前言 JavaScript中this变量是一个令人难以摸清的关键字,当初学习javascript的时候被这个this指向问题折腾的我是惨不忍睹,漏洞百出.一度想在后面的代码过程中放弃对this的使用, ...
- 浅谈 volatile 的实现原理
在并发编程中我们一般都会遇到这三个基本概念:原子性.可见性.有序性.我们稍微看下volatile 原子性 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行. ...
- 浅谈transient关键字
1,用途 当一个对象实现了Serilizable接口,这个对象就可以被序列化.而有时候我们可能要求:当对象被序列化时(写入字节序列到目标文件)时,有些属性需要序列化,而其他属性不需要被序列化,打个比方 ...
随机推荐
- 最简单的基于FFMPEG的Helloworld程序
===================================================== 最简单的基于FFmpeg的视频播放器系列文章列表: 100行代码实现最简单的基于FFMPEG ...
- [Java多线程] volatile 关键字正确使用方法
volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性,即多线程环境中,使用 volatile 关键字的变量仅可以保证不同线程读取变量时,可以读到最新修改的变量值,但是 ...
- jQuery remove()与jQuery empty()的区别
jQuery remove() 方法删除被选元素及其子元素.举例如下: <!DOCTYPE html> <html> <head> <script src=& ...
- 解决Error:Unable to find method 'org.gradle.api.internal.project.ProjectInternal.
错误描述今天在Github上面下载了一份代码,然后导入到Android Studio中直接报错误 错误描述如下: Error: Unable to find method ‘org.gradle.ap ...
- (linux)struct inode 和 struct file
转自:http://www.cnblogs.com/QJohnson/archive/2011/06/24/2089414.html 1.struct inode──字符设备驱动相关的重要结构介绍 内 ...
- [RK3288][Android6.0] 调试笔记 --- 移除uboot和kernel开机logo【转】
本文转载自:http://blog.csdn.net/kris_fei/article/details/71600690 Platform: RockchipOS: Android 6.0Kernel ...
- ffmpeg 中av_rescale_rnd 的含义
http://blog.csdn.net/fireroll/article/details/8485482 一.函数声明: int64_t av_rescale_rnd(int64_t a, int6 ...
- [HAOI 2012] 容易题
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=2751 [算法] 考虑k = 0的情况 , 根据乘法原理 : Ans = (n * ( ...
- 转:Ubuntu12.04编译VLC,在linux上运行
Ubuntu12.04编译vlc2.1.0 1.编译环境 VM8.0.1 # gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAP ...
- dubbo 使用 filter 报错解决
dubbo可以用filter实现类似tomcat filter过滤器. 实现1.接口请求时间监控. 2.打印输入输出日志(输出日志有应用自己决定) 配置时出现报错. No such extension ...