浅谈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接口,这个对象就可以被序列化.而有时候我们可能要求:当对象被序列化时(写入字节序列到目标文件)时,有些属性需要序列化,而其他属性不需要被序列化,打个比方 ...
随机推荐
- 九度OJ 1127:简单密码 (翻译)
时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:1218 解决:721 题目描述: Julius Caesar曾经使用过一种很简单的密码. 对于明文中的每个字符,将它用它字母表中后5位对应的 ...
- 【LeetCode】Sqrt(x) (转载)
Implement int sqrt(int x). Compute and return the square root of x. 原文地址: http://kb.cnblogs.com/page ...
- machine learning for hacker记录(3) 贝叶斯分类器
本章主要介绍了分类算法里面的一种最基本的分类器:朴素贝叶斯算法(NB),算法性能正如英文缩写的一样,很NB,尤其在垃圾邮件检测领域,关于贝叶斯的网上资料也很多,这里推荐那篇刘未鹏写的http://mi ...
- Cocos2d-x如何添加新场景及切换新场景(包括场景特效)
做了一天多的工作终于把此功能搞定了,实际上添加新场景花费不了多少时间,时间主要花在切换到另一个场景的实现上,主要原因是编译时出现了一个错误,百思不得其解,后来经过查资料不断摸索才知道自己问题的所在,改 ...
- DIY固件系列教程——实现开机LOGO三屏动画的完全替换【转】
本文转载自:http://blog.csdn.net/sdgaojian/article/details/9192433 本教程需要用到如下工具:1,7Z压缩工具2,AddCrc32效验工具3,raw ...
- Vue实例和方法
github地址:https://github.com/manlili/vue_learn里面的lesson03 一 实例 每个 Vue 实例都会代理其 data 对象里所有的属性,改变data,vu ...
- javase练习题--每天写写
package com.javaTestDemo; import java.util.Scanner; public class JavaTest1 { public static void main ...
- servlet串行拦截器实现例子
至于串行过滤器有什么作用,我实在不知.我的理解是它只是说明 过滤器的串行运行方式 需求:当用户没有登录访问更新页面的时候,跳转到登录页面 1.登录页面:login.jsp <%@ page la ...
- CI 模型公用查询函数
/** * 多字段条件查询数据 * @param array $val array("name" => $value).name为要操作的字段,value为要操作的值 * @ ...
- 静态路由配置及RIP配置实验
[实验环境] Packet Trace 5.3 模拟软件. [实验步骤] 1.首先要进行IP地址规划.(例如下图格式) 网络名 网络地址 子网掩码 网关 主机IP vlan1 10.10.1.0 25 ...