volatile的原理分析
前言:Volatile作为一个多线程开发中的强有力的轻量级的线程协助工具,在实际编程中随处可见,它比synchronized更加轻量和方便,消耗的资源更少,了解Volatile对后面了解多线程有很重要的意义,本篇博客我们就来探究如果在一个字段上加上Volatile,那么它实际上到底起了什么作用?以及是怎么工作的?
本篇博客的目录:
一:工作内存和主内存
二:volatile的两大作用
三:volatile一定是线程安全的吗?
四:Volatile的局限性和适用场景
五:总结
正文开始
一:工作内存和主内存
1.1:它们具有的特点
①主内存是所有变量的存储地方,这包括所有你看到的变量包括实例变量、静态字段、数组对象的元素
②工作内存是线程私有的,所有的线程在操作变量(读取或者赋值)的时候都必须在工作内存中完成,而不能在主内存中进行
③不同的线程之间无法访问对方的工作内存中的变量,线程之间传递值需要在工作内存中进行
1.2: 图示
该图主要模拟了5个线程的工作内存和主内存之间的交互,可以看出不同线程之间是不可以进行变量交换的,它们公用一个主内存,所有的变量传递都在主内存中进行完成

二:volatile的两大作用
2.1:线程可见性
这里的可见性是指若一个变量被Volatile修饰,那么假如A线程对其进行了修改操作,那么其他线程都会立刻拿到修改后的值,Volatile能使一个变量在各个线程中达到线程一致性;
1.2:禁止指令重排序
普通变量在方法执行的过程中,它的执行顺序并不一定是程序代码的执行书序,但是它保证了所有依赖赋值的结果都能获取到正确的结果,线程在执行过程中无法知道这一点的,这也就是“线程表现为串行的含义”。
这里的执行顺序会受到指令重排序(硬件级别的)的影响。而volatile则会给代码添加一个内存屏障,指令重排序的时候不会把后面的指令重排序到屏障的位置之前。
ps:只有一个cpu的时候,这种内存屏障是多余的。只有多个cpu访问同一块内存的时候,就需要内存屏障了。
三:volatile一定是线程安全的吗?
3.1:实际例子
public class VolatileTest {
private static int thread_nums=100;
public static volatile int num=0;
public static int increment(){
return num++;
}
public static void main(String[] args) {
changeNum();
while (Thread.activeCount()>1) {
Thread.yield();
}
System.out.println(num);
}
private static void changeNum() {
Thread[] threads=new Thread[thread_nums];
for (int i = 0; i < thread_nums; i++) {
threads[i]=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < thread_nums; i++) {
increment();
}
}
});
threads[i].start();
}
}
}
这则程序开启了100个线程,然后让每个线程都给num值+1,理论上最后的运行结果应该是1000,但是实际上的运行效果最后都小于这个数字,看以下的运行结果:



3.2:上述程序的分析
为什么结果会出现小于预期值1000呢,这是因为在字节码运行过程中,当某一个线程(假设为线程A)把num值取到操作栈顶的时候,Volatile关键字保证了num在此时是正确的, 正当线程A要把num同步到主内存的时候,其它线程(假设为线程B)可能已经把num值加大了,这个时候再把num同步回去,此时的num值刷新为线程A的值就变小了,而其它线程在取这个值调用increment方法就小于最终的预期值了。
3.3:补救措施
3.3.1:第一种补救措施很简单,就是简单粗暴的的加锁,这样可以保证给num加1这个方法是同步的,这样每个线程就会井然有序的运行,而保证了最终的num数和预期值一致。
public static synchronized int increment(){
return num.incrementAndGet();
}
3.3.2:把num声明为原子的AtomicInteger
public static AtomicInteger num =new AtomicInteger();
public static int increment(){
return num.incrementAndGet();
}
AtomicInteger这是个基于CAS的无锁技术,它的主要原理就是通过比较预期值和实际值,当其没有异常的以后,就进行增值操作,incrementAndGet这个方法实际上每次对num进行+1的过程都进行了无法次的比较,存在一个retry的过程,而它在多线程处理中可以防止这种多次递增而引发的线程不安全的问题
四:Volatile的局限性和适用场景
4.1:适用Volatile的优势
Volatile作为一种轻量级的同步工具,它比Synchronzied拥有更少的资源消耗。但是更严谨的话,因为虚拟机对锁实行的很多消除和优化,使得我们很难量化的认为Volatile就一定比Synchronized快多少。
如果让Volatile与自己进行比较的话,它在读操作的性能消耗与普通的没有额外处理的变量没有任何区别,但是在写操作上会慢一点,因为它需要在代码中插入很多内存屏障指令来保证多个cpu下不会发生乱序操作。但是绝大多数情况下,volatile还是要比synchronized的总开销要低很多
4.1:非原子操作
voatile变量同样存在变量不一致的情况,这是因为java里面的运算并非原子操作,导致volatile运算在并发情况下不一定是线程安全的!另外64位的数据类型(long和double),如果没有volatile修饰的话,那么虚拟机将会将其读写划分为两次32位的操作来进行,虚拟机可以选择不保证64位数据类型的操作原子性,这也就是long和double的非原子性协定;volatile本身不保证获取和设置操作的原子性,仅仅保持修改的可见性。而java内存模型保证声明为volatile的long和double变量的get和set操作是原子的
4.2:适用场景
Volatile适用于维护状态变量的值,一般为boolean状态量,这个状态会触发一定的条件,而用Volatile就能对这种条件进行安全的临界处理。下面举一个简单的例子:
public class UseVolatile {
private volatile boolean open=false; //状态变量open
public void close(){
open=false;
}
public void doSth(){
while (!open) {
//do sth
}
}
}
Volatile维护了一个状态条件open,而doSth方法则依赖于这个状态变量,而Volatile变量修饰的话,它总会保持最新的值,这样doSth()方法执行的时候,触发while(!open)这个机制的时候,会保证它取到最近的状态,最终
保证了程序正确执行。
五:总结
本篇博文先是简单介绍了java的内存模型,包括工作内存和主内存,描述了工作内存和主内存的特点,而后又分析了Volatile的两大作用,并且探讨了Volatile的线程安全性,以及优势和使用场景等,旨在理解Volatile的作用,了解它的本质,体会它的不足,从而在实际的开发工作中能够游刃有余的使用这个工具。
volatile的原理分析的更多相关文章
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
- JMM和Volatile底层原理分析
JMM和volatile分析 1.JMM:Java Memory Model,java线程内存模型 JMM:它是一个抽象的概念,描述的是线程和内存间的通信,java线程内存模型和CPU缓存模型类似,它 ...
- 原子类java.util.concurrent.atomic.*原理分析
原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...
- NOR Flash擦写和原理分析
NOR Flash擦写和原理分析 1. NOR FLASH 的简单介绍 NOR FLASH 是很常见的一种存储芯片,数据掉电不会丢失.NOR FLASH支持Execute On Chip,即程序可以直 ...
- 消息队列NetMQ 原理分析2-IO线程和完成端口
消息队列NetMQ 原理分析2-IO线程和完成端口 前言 介绍 目的 IO线程 初始化IO线程 Proactor 启动Procator线程轮询 处理socket 获取超时时间 从完成端口获取处理完的状 ...
- 消息队列NetMQ 原理分析4-Socket、Session、Option和Pipe
消息队列NetMQ 原理分析4-Socket.Session.Option和Pipe 前言 介绍 目的 Socket 接口实现 内部结构 Session Option Pipe YPipe Msg Y ...
- HashMap 与 ConcrrentHashMap 使用以及源码原理分析
前奏一:HashMap面试中常见问题汇总 HashMap的工作原理是近年来常见的Java面试题,几乎每个Java程序员都知道HashMap,都知道哪里要用HashMap,知道HashTable和Has ...
- 并发之volatile底层原理
15.深入分析Volatile的实现原理 14.java多线程编程底层原理剖析以及volatile原理 13.Java中Volatile底层原理与应用 12.Java多线程-java.util.con ...
- ZT自老罗的博客 Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析
Android系统的智能指针(轻量级指针.强指针和弱指针)的实现原理分析 分类: Android 2011-09-23 00:59 31568人阅读 评论(42) 收藏 举报 androidclass ...
随机推荐
- TopCoder SRM 489 Div1 Lev3:AppleTree
挺优秀的一道题,想出做法时有些惊艳. 题意: 数轴上有\(D\)个连续整数刻度,有\(N\)棵树要种在这些刻度上,其中第\(i\)棵与两旁(如果有的话)相邻的树至少要相距\(R_i\),问方法数. \ ...
- 如何在WIN7_64环境下安装Oracle10g_64位版本
转载请注明出处http://www.cnblogs.com/SharpL/p/4338638.html 1.如果之前安装过Oracle软件,建议完全卸载(究竟有没有必要_不知道_我是这么做的) 2.清 ...
- 4 class类 web服务器
1.换行符 2.pycharm 连接Ubuntu 1)添加环境变量 2)查看ip 3)配置目录 4)上传或者下载 3.面向对象抽象web服务器 1)版本1:类 class HttpServer(obj ...
- 3122 奶牛代理商 VIII(状压dp)
3122 奶牛代理商 VIII 时间限制: 3 s 空间限制: 256000 KB 题目等级 : 大师 Master 题目描述 Description 小徐是USACO中国区的奶牛代理商 ...
- 「日常训练」「小专题·USACO」 Broken Necklace(1-2)
题意 圆形链条,打断一处可以形成一条链.问在哪个地方开始打断,能够形成最大的连续颜色(白色视作同样的颜色)? 分析 说起来很高级,但是我们实际上并不需要穷举打断的地方,只需要把串重复三回啊三回.然后从 ...
- Kotlin操作符重载:把标准操作加入到任何类中(KAD 17)
作者:Antonio Leiva 时间:Mar 21, 2017 原文链接:https://antonioleiva.com/operator-overload-kotlin/ 就像其他每种语言一样, ...
- you need to resolve your current index first 已解决
从一个分支A切换到另一个分支B后,对切换后的B分支进行pull操作,因为pull操作实际上包含了fetch+merge操作,在执行 merge操作时,由于很长时间没有对B分支执行过pull/merge ...
- ThinkPHP5项目目录规划实践
ThinkPHP5安装后(或者下载后的压缩文件解压后)可以看到下面的目录结构: tp5├─application 应用目录 ├─extend 扩展类库目录(可定义) ├─pu ...
- HDU 4587 TWO NODES(割点)(2013 ACM-ICPC南京赛区全国邀请赛)
Description Suppose that G is an undirected graph, and the value of stab is defined as follows: Amon ...
- LTE:上行调度请求(Scheduling Request,SR) LTE:下行资源分配类型
http://blog.sina.com.cn/s/blog_927cff010101a7yh.html 上行调度请求(Scheduling Request,SR) 如果UE没有上行数据要传输,eNo ...