关键字volatile是Java虚拟机提供的最轻量级的同步机制,但是在平时的项目里面,遇到需要多线程的时候更多地使用的是synchronized关键字来进行同步。个人而言,更多的原因是对volatile关键字的机制不了解导致的。

Java内存模型对volatile专门定义了一些特殊的访问规则,当一个变量定义为volatile之后便具有了两种特性:

1. 保证此变量对所有线程的可见性,“可见性”指当一条线程修改了这个变量的值,新的值对与其他线程来说是立即得知的。

2. 禁止指令重排序优化。

接下来将对上述两个方面分别介绍:

普通变量的值在县城之间传递均需要通过主内存来完成,例如,线程A修改一个普通变量的值,然后向内存进行会写,另外一条线程B在线程A回写完之后再从主内存中进行读取操作,新变量值才会对线程B可见。

尽管volatile定义的变量对所有的线程都是可见的,但是并不能说明volatile定义的变量的运算在并发下就是安全的。

(在各个线程的工作内存中,volatile变量也可以存在不一致的情况,但是由于每次使用之前都已经刷新了,执行引擎看不到不一致的情况,所以便认为不存在不一致的情况)

导致不安全的原因其实还是Java运算是非原子操作。所谓原子操作是指操作的执行不会被线程的调度给打断

可以看这个例子:

public class VolatileTest {
public static volatile int race = 0; public static void increase(){
race++;
} private static final int THREAD_COUNT = 20; public static void main(String[] args) {
Thread[] threads = new Thread[THREAD_COUNT];
for(int i = 0; i < THREAD_COUNT; i++){
threads[i] = new Thread(new Runnable() { @Override
public void run() {
for(int i = 0; i < 1000; i++){
increase();
}
}
});
threads[i].start();
}
while(Thread.activeCount() > 1){
Thread.yield();
}
System.out.println(race);
}
}

20个线程,每个线程会对race变量进行1000次自增操作,即race++。输出的结果应该是20000,但是会发现每次运行的结果都不一样,而且都是一个小于20000的数。

导致的原因是race++操作并不是一个原子操作,尽管看来它只有一句话,但是在编译时并不是这样的。用javap命名进行反编译,同时输出附加信息:

increase()方法的执行一共是四条字节码指令完成的,熟悉字节码命令的可以看出着四步的操作,

当getstatic指令将race的值取到操作栈的时候,volatile保证了race的值是正确的,但是在执行后面的指令的时候,其它的线程可能已经把race的值修改了。

即使编译出来的只有一条字节码指令,但也不意味这是一个原子操作。

由于volatile变量智能保证可见性,依然需要synchronized和java.util.concurrent中的原子类来保证线程的安全。

下面这段代码就展示了一个很好的volatile的使用场景:

volatile Boolean shutdownRequset;
public void shutdown() {
shutdownRequset = true;
} public void doWork() {
while(!shutdownRequest){
...
}
}

当shutdown()方法被调用的时候,能保证所用的doWork()方法都停下来。

——————————————————————————————————————————————————————————————————————

接下来是第二个方面的用途,普通变量仅仅会保证在该方法执行过程中所有依赖赋值结果的地方都能获得正确的结果,而不能保证变量赋值的操作顺序与程序代码的执行顺序一致。

如果定义的变量没有被volatile修饰,那么就有可能由于指令重排的优化而导致执行顺序的颠倒。

如下面这段代码:

public class Singleton {

    private volatile static Singleton instance;

    public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class) {
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
} public static void main(String[] args) {
Singleton.getInstance();
}
}

通过JIT编译之后就会多执行一个“lock”操作,这个操作相当于一个内存屏障,指重排序之后,不能讲屏障之后的操作放到屏障之前。

不过说到底并不能说volatile有什么执行的迅速的特点,但其开销是比锁低的,,唯一需要看的是volatile是否满足使用场景。

volatile关键字是如何起作用的?的更多相关文章

  1. 实习第一个月总结(const关键字、条件编译、volatile关键字、#和##的作用、函数指针)

    C语言中const关键字的作用: 修饰局部变量或者全局变量,表示变量n的值不能被改变了 修饰指针,分为常量指针与指针常量,也可以两者结合 常量指针指向的值不能改变,但是这并不是意味着指针本身不能改变, ...

  2. Volatile关键字的两个作用

    1.保证修饰的变量对所有线程的可见性,这里的“可见性”是指当一条线程修改了这个值,新值对于其他线程来说是可以立即得知的. 2.禁止指令重新排序化

  3. [其他]volatile 关键字

    用  volatile 关键字修饰函数 的作用是 告诉编译器该函数不会返回 , 让编译器能产生更好的代码 另外也能避免一些假警告信息,如未初始化的变量等

  4. [面试必备]深入理解Java的volatile关键字

    前言 在Java并发编程中,volatile关键字有着至关重要的作用,在面试中也常常会是必备的一个问题.本文将会介绍volatile关键字的作用以及其实现原理. volatile作用 volatile ...

  5. Volatile关键字回顾之线程可见性

    java中,volatile关键字有两大作用: 1.保证线程的可见性 2.防止指令重排序 这篇文章主要通过典型案例,体现可见性这一特性. 概念: java中,堆内存是线程共享的.而每个线程,都应该有自 ...

  6. Java并发编程——为什么要用volatile关键字

    首发地址 https://blog.leapmie.com/archives/66ba646f/ 日常编程中出现 volatile 关键字的频率并不高,大家可能对 volatile 关键字比较陌生,再 ...

  7. 深入解析volatile关键字

    前言 很高兴遇见你~ 欢迎阅读我的文章. volatile关键字在Java多线程编程编程中起的作用是很大的,合理使用可以减少很多的线程安全问题.但其实可以发现使用这个关键字的开发者其实很少,包括我自己 ...

  8. 【面试普通人VS高手系列】volatile关键字有什么用?它的实现原理是什么?

    一个工作了6年的Java程序员,在阿里二面,被问到"volatile"关键字. 然后,就没有然后了- 同样,另外一个去美团面试的工作4年的小伙伴,也被"volatile关 ...

  9. Java并发编程学习笔记 深入理解volatile关键字的作用

    引言:以前只是看过介绍volatile的文章,对其的理解也只是停留在理论的层面上,由于最近在项目当中用到了关于并发方面的技术,所以下定决心深入研究一下java并发方面的知识.网上关于volatile的 ...

随机推荐

  1. Require,js配置使用心得

    首先大家要知道requirejs是干嘛用的,要解释,那就用一句话说下:RequireJS是一个JavaScript文件和模块加载器接下来我们开始学会配置使用requireJs,当然在学习使用的过程中也 ...

  2. C#基础,目录

    首先,要说明一下本系列会以使用C#为侧重点,不会系统的.大篇量的去解说一些名词,比如:runtime.IL等.文章会在合适的时候对用到的基本概念进行简述.如果你是初学者,建议你也不要过度的去纠结,等你 ...

  3. CentOS6.9-zabbix3.2启动失败原因及页面没有mysql选择项

     环境内核信息: [root@zabbix- ~]# uname -a Linux lodboyedu- -.el6.x86_64 # SMP Tue Mar :: UTC x86_64 x86_64 ...

  4. 开发指南专题六:JEECG微云高速开发平台代码生成

    开发指南专题六:JEECG微云高速开发平台代码生 1.1. 代码生成扫描路径配置 用代码生成器生成代码后.须要进行相关配置配置,扫描注入control.service.entity等; 具体操作过程例 ...

  5. Mac: Android studio+VirtualBox+Genymotion

    针对 Mac Yosemite 10.10.4 操作系统 1.在Android Studio 中preferences 中的Plugins 中安装Genymotion插件. Android Studi ...

  6. 《github一天一道算法题》:并归排序

    看书.思考.写代码! /******************************************* * copyright@hustyangju * blog: http://blog.c ...

  7. 不用asp.net MVC,用WebForm照样能够实现MVC

    在<避开WebForm天坑,拥抱ASP.Net MVC吧>这篇博客中我讲到了ASP.net WebForm由于一些先天的"诱导犯罪"的缺陷,如今用ASP.net MVC ...

  8. exports 和 module.exports

    首先参考一个js的示例 app.js var a = {name: 'nswbmw 1'}; var b = a; console.log(a); console.log(b); b.name = ' ...

  9. 你懂AI吗(1)

    那场载入史册的战争之后,AI成为地球的主人已经快一个世纪了. 随着见证这场战争的那一代人基本消失,除了几个要堵上人类的尊严,颠覆AI的邪恶统治的组织外,现在的人基本已经习惯了这个AI统治的世界. AI ...

  10. MyBatis_延迟加载01

    一.延迟加载 MyBatis中的延迟加载,也称为懒加载,是指在进行关联查询时, 按照设置延迟规则推迟对关联对象的select查询.延迟加载可以有效的减少数据库压力. MyBatis的延迟加载只是对关联 ...