关于关键字volatile可以说是Java虚拟机提供的轻量级的同步机制,但是它并不容易完全被正常、完整地理解,以至于许多程序员都不习惯去使用它,遇到需要处理多线程数据竞争问题的时候一律使用Synchronized来进行同步。了解volatile变量的语义对了解多线程操作的其他特性很有意义。

当一个变量定义为volatile后,它将具备两种特性,第一是保证此变量对所有线程的可见性,这里的”可见性“是指当一条线程修改了这个变量的值,新值对于其它线程来说是可以立即得知的。而普通变量是做不到这一点,普通变量的值在线程间传递均需要通过主内存来完成,例如,线程A修改一个普通变量的值,然后向主内存进行回写,另外一条线程B在线程A回完成了之后再从主内存进行读取操作,新变量值才会对线程B可见。

特性一:可见性

介绍到这里可能很多朋友会说,并发线程吗我把变量声明称volatile就可以了嘛,哪里有那么复杂,事实并非如此,看段代码:

import java.util.concurrent.*;

/**
* by lv xiao long
*/
public class App {
public static volatile int count; public static void increase() {
++count;
} private static final int THREADS_COUNT = 10; private static CountDownLatch countDownLatch=new CountDownLatch(10); public static void main(String[] args) {
// 线程池
ExecutorService exec = Executors.newCachedThreadPool();
// 启动10条线程,每条线程连续自增1000次。最后count等于10*1000对吗?
for (int index = 0; index < THREADS_COUNT; index++) {
Runnable run = new Runnable() {
public void run() {
for (int i=0;i<1000;i++){
increase();
}
countDownLatch.countDown();
}
};
exec.execute(run);
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//等待所有线程结束
exec.shutdown();
System.out.print(count);
}
}

  最后结果你会看到不是10*1000,按道理来说在线程中使用volatile变量,每次使用之前都要先刷新,执行引擎看不到不一致情况 ,因此不存在一致性问题,但是在java里面运算并非原子操作,导致volatitle变量的运算在并发下一样是不安全的。

由于volatitle变量只能保证可见性,在不符合以下两条规则的运算场景中,我们仍然要通过加锁(使用synchronized或java.util.conrueent中的原子类)来保证原子性。

规则1:运行结果并不依赖变量当前值,或者能够确保只有单一线程修改变量的值。

规则2:变量不需要与其他的状态变量共同参与不变约束。

例如下面场景:

volatile boolean off;

public void shutdown(){
off=true;
} public void doWork(){ while(!off){
//do stuff
}
}

特性二:禁止指令重排序优化

普通的变更仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中的执行顺序一致。因为在一个线程的方法执行过程中无法感知到这点,这也就是java 内存模型中描述的所谓的”线程内表现为串行的语义“

Map configOptions;

char[] configText;

//此变量必须定义为volatile
volatile boolean initialized = false; //假设以下代码在线程A中执行
configOptions=new HashMap(); configText=readConfigFile(fileName); processConfigOptions(configText,configOptions); initialized =true; //假设以下代码在线程B中执行
while(!initialized ){ sleep(); }
doSomethingWithConfig();

上面代码定义initialized 变量时没有使用volatile修饰,就可能会由于指令重排序优化,导致位于线程A中最后一句代码”initialized =true“提前被执行,得不到最终你想要的结果。

如果在面试过程中,遇到有关volatile相关的话题,可以简明扼要的说明volatile两种特性,以及适用的一些场景,个人觉得一般都能通过。

java volatitle介绍与使用的更多相关文章

  1. Android下HelloWorld项目的R.java文件介绍

    R.java文件介绍 HelloWorld工程中的R.java文件 package com.android.hellworld; public final class R {     public s ...

  2. 深入Java虚拟机读书笔记第一章Java体系结构介绍

    第1章 Java体系结构介绍 Java技术核心:Java虚拟机 Java:安全(先天防bug的设计.内存).健壮.平台无关.网络无关(底层结构上,对象序列化和RMI为分布式系统中各个部分共享对象提供了 ...

  3. java集合介绍(List,Set,Map)

    前言 介绍java的常用集合+各个集合使用用例 欢迎转载,请注明作者和出处哦☺ 参考: 1,<Java核心编程技术(第二版)> 2, http://www.cnblogs.com/Litt ...

  4. Java学习介绍

    Java版本介绍 JavaME:微型版,用于开发小型设备.智能卡.移动终端应用(使用率较低) JavaSE:标准版,用于创建桌面应用(企业用JavaSE创建桌面应用较少) JavaEE:企业版,用于创 ...

  5. 流行的9个Java框架介绍: 优点、缺点等等

    流行的9个Java框架介绍: 优点.缺点等等 在 2018年,Java仍然是世界上最流行的编程语言.它拥有一个巨大的生态系统,在全世界有超过900万Java开发人员.虽然Java不是最直接的语言,但是 ...

  6. Java监控工具介绍,VisualVm ,JProfiler,Perfino,Yourkit,Perf4J,JProbe,Java微基准测试【转】

    Java监控工具介绍,VisualVm ,JProfiler,Perfino,Yourkit,Perf4J,JProbe,Java微基准测试[转] 本文是本人前一段时间做一个简单Java监控工具调研总 ...

  7. java JNI介绍

    java JNI介绍 目录 java JNI介绍 1. Java调用C++代码 2.C++代码调用java代码 JNI是Java Native Interface的全称. oracle文档中是这样描述 ...

  8. Java秘诀!Java逻辑运算符介绍

    运算符丰富是 Java 语言的主要特点之一,它提供的运算符数量之多,在高级语言中是少见的. Java 语言中的运算符除了具有优先级之外,还有结合性的特点.当一个表达式中出现多种运算符时,执行的先后顺序 ...

  9. Java秘诀!Java赋值运算符介绍

    运算符丰富是 Java 语言的主要特点之一,它提供的运算符数量之多,在高级语言中是少见的. Java 语言中的运算符除了具有优先级之外,还有结合性的特点.当一个表达式中出现多种运算符时,执行的先后顺序 ...

随机推荐

  1. source install sshpass in aix

    1.源码下载:   wget https://nchc.dl.sourceforge.net/project/sshpass/sshpass/1.06/sshpass-1.06.tar.gz 2.解压 ...

  2. 资深小白带你走进OS Memory

    图片来源:http://www.tomshardware.com/ 序言: Memory(内存)是一台计算机组成的重要部分,也是最基础的一部分.其它基础组件有主板.CPU.磁盘.显卡(可独立可集成)等 ...

  3. 数据库并行读取和写入(Python实现)

    这篇主要记录一下如何实现对数据库的并行运算来节省代码运行时间.语言是Python,其他语言思路一样. 前言 一共23w条数据,是之前通过自然语言分析处理过的数据,附一张截图: 要实现对news主体的读 ...

  4. PHP中的面向对象OOP中的魔术方法

    一.什么是魔术方法: PHP为我们提供了一系列用__开头的函数,这些函数无需自己手动调用,会在合适的时机自动调用,这类函数称为魔术函数.例如: function __construct(){} 在ne ...

  5. thinkphp获取特定字段的两种方法

    thinkphp getField( )和field( ) 2014年10月05日 ⁄ 综合 ⁄ 共 1509字 ⁄ 字号 小 中 大 ⁄ 评论关闭 做数据库查询的时候,比较经常用到这两个,总是查手册 ...

  6. java 实例变量的初始化

    1.对于实例变量,该类没创建一次实例,就需要为实例变量分配一块内存空间:2.程序通过Person对象来访问eyeNum类变量时,底层依然会转换为通过Person访问eyeNum类变量:3.当Perso ...

  7. 正则表达式去除字符串左右空格函数 调用方法是,str.Trim();

    正则表达式去除字符串左右空格函数 调用方法是,str.Trim(); String.prototype.Trim = function() { return this.replace(/(^\s*)| ...

  8. Android 4.0以后正确的获取外部sd卡存储目录

    刚解决这个棘手的问题 找了很久,随笔记下. 网上搜索 android 获取外部sd卡存储目录 普遍都是: 1) Environment.getExternalStorageDirectory() 这个 ...

  9. js中的数字格式变成货币类型的格式

    <!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8& ...

  10. Mybatis中使用 #{} 和 ${} 向sql传参时的区别

    今天在工作时,使用MyBatis中向sql传递两个参数时,一直显示SQL语法错误,仔细检查,才发现传入的参数被加上了引号,导致传入的参数(要传入的参数是表名)附近出现语法错误. 错误写法: } a } ...