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关键字的更多相关文章

  1. 浅谈Volatile与多线程

        标题:浅谈Volatile与多线程 2011-04-19 22:49:17 最近看的比较杂,摘了一些人的笔记!随着多核的日益普及,越来越多的程序将通过多线程并行化的方式来提升性能.然而,编写正 ...

  2. 浅谈Static关键字

    1.使用static关键字声明的属性为全局属性 未使用static关键字指定city之前,如果需要将Tom,Jack,Mary三人的城市均改成Beijing,需要再次声明三次对象的city为Beiji ...

  3. 浅谈Dynamic 关键字系列之一:dynamic 就是Object(转)

    C# 4.0提供了一个dynamic 关键字,那么什么是dynamic,究竟dynamic是如何工作的呢? 从最简单的示例开始: static void Main(string[] args) { d ...

  4. 浅谈 var 关键字

    提起 var关键子,程序员的第一反应就是JavaScript, 事实上这个关键子在其他语言中也有被采用. 比如说C#, 比如说kotlin, 用法和JavaScript中使用差不多,作为要声明变量的前 ...

  5. 浅谈this关键字

    在我学习this关键字的时候,通过查找资料总结出一些this的特殊用法, 供大家参考,代码里面有我总结的分析过程! 箭头函数里的this: var username = "全局"; ...

  6. 浅谈final关键字的用法

    1.final变量: 常和static一起使用,修饰成员变量或者本地变量.修饰后为常量,不可以再次初始化(再次引用),例如public static final String SUCCESS= &qu ...

  7. 浅谈javascript-this关键字

    前言 JavaScript中this变量是一个令人难以摸清的关键字,当初学习javascript的时候被这个this指向问题折腾的我是惨不忍睹,漏洞百出.一度想在后面的代码过程中放弃对this的使用, ...

  8. 浅谈 volatile 的实现原理

    在并发编程中我们一般都会遇到这三个基本概念:原子性.可见性.有序性.我们稍微看下volatile 原子性 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行. ...

  9. 浅谈transient关键字

    1,用途 当一个对象实现了Serilizable接口,这个对象就可以被序列化.而有时候我们可能要求:当对象被序列化时(写入字节序列到目标文件)时,有些属性需要序列化,而其他属性不需要被序列化,打个比方 ...

随机推荐

  1. checkbox 背景图片 纯CSS处理办法

    CSS .table_container input[type="checkbox"] { background: #fff url(/img/blue.png); backgro ...

  2. signal函数理解或者void (*signal(int signum,void(*handler)(int)))(int)理解

    把void (*signal(int signum,void(*handler)(int)))(int)分成两部分: typedef void (*sighandler_t)(int); sighan ...

  3. Android 6.0 如何默认打开user版本的root权限【转】

    本文转载自:http://blog.csdn.net/wangjicong_215/article/details/77601638 1.system/core/adb/Android.mkdiff ...

  4. YCSB-mapkeer-leveldb实测

    使用thrift0.8.0编译好java版的mapkeeper并安装到ycsb下,使用thrift0.9.2编译好c++版的mapkeeper并编译leveldb客户端运行. 测试成功.recordc ...

  5. HTTP上传大文件要考虑的问题

    1.大文件上传服务器内存占用 一般WEB开发框架如SpringMVC,在基于Web容器如Tomcat处理HTTP请求时,都倾向于采用职责链流水线式的处理机制.HTTP请求被封装为一个可解析对象放在内存 ...

  6. bzoj 4756 [Usaco2017 Jan]Promotion Counting——线段树合并

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4756 线段树合并裸题.那种返回 int 的与传引用的 merge 都能过.不知别的题是不是这 ...

  7. TFS独占签出代码

    最近发现微软给我们提供了免费的TFS,地址:http://tfs.visualstudio.com/, 就注册了一个,但是我发现没办法独占签出. 在公司里,TFS有服务端,所以很好设置,但是注册微软的 ...

  8. SIM卡(单卡)配置

    SIM卡相关配置 1.GPIO90--->BPI8 GPIO91--->BPI9 GPIO92--->BPI10 2.ProjectConfig.mk:MTK_PROTOCOL1_R ...

  9. moco实例

    一.moco模拟接口响应json moco的下载地址见虫师博客园:https://www.cnblogs.com/fnng/p/7511539.html foo.json文件内容如下 [ { &quo ...

  10. 十、外键约束FK(foreign key)

    1.定义 a.外键涉及到的术语:外键约束.外键字段.外键值. b.外键约束.外键字段.外键值三者之间的关系? 答:给某个字段添加外键约束之后,该字段称为外键字段,外键字段中的值是外键值. c.外键根据 ...