一 . 可见性(visibility)

volatile关键字修饰的变量,如果值发生了改变,其他线程会立刻获取到,从而避免了出现脏读的情况。

 public class TestVolatile {

     public static void main(String[] args) {
MyData myData = new MyData();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("进入操作数据线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用方法 赋值
myData.changeData();
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"操作数据线程").start(); // 主线程查看数据是否改了
while (myData.data == 0){ }
System.out.println("main线程结束");
}
} class MyData{
int data = 0;
public void changeData(){
this.data = 2020;
} }

如上面代码,有两个线程在操作MyDdata数据类,看一下执行结果

从结果可以看出,main线程一直就没有获取到数据更新信息,内存中的数据存储用图直观的看一下

main线程的内存线程并没获取到数据更新。

下面变量加上volatile的效果

 public class TestVolatile {

     public static void main(String[] args) {
MyData myData = new MyData();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("进入操作数据线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用方法 赋值
myData.changeData();
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"操作数据线程").start(); // 主线程查看数据是否改了
while (myData.data == 0){ }
System.out.println("main线程结束");
}
} class MyData{
volatile int data = 0;
public void changeData(){
this.data = 2020;
} }

看一下执行结果

发现main方法已经获取到了数据更新。从而验证了volatile的可见性。

二 . 无法保证原子性

直接上代码

 public class TestVolatile1 {

     public static void main(String[] args) {
MyData1 myData = new MyData1(); new Thread(new Runnable() {
@Override
public void run() {
//调用方法 赋值
myData.changeData();
for(int i = 0;i < 9999;i++) {
myData.changeData();
}
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"线程1").start(); new Thread(new Runnable() {
@Override
public void run() {
//调用方法 赋值
myData.changeData();
for(int i = 0;i < 9999;i++) {
myData.changeData();
}
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"线程2").start(); new Thread(new Runnable() {
@Override
public void run() {
//调用方法 赋值
myData.changeData();
for(int i = 0;i < 9999;i++) {
myData.changeData();
}
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"线程3").start(); new Thread(new Runnable() {
@Override
public void run() {
//调用方法 赋值
myData.changeData();
for(int i = 0;i < 9999;i++) {
myData.changeData();
}
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"线程4").start(); while (Thread.activeCount() > 1) {
Thread.yield();
} System.out.println("最终数值 :"+myData.data);
}
} class MyData1{
volatile int data = 0;
public void changeData(){
data++;
} }

咱们可以预测一下,如果正常的话,咱们应该得到的最终数据应该是40000 ,但结果如下

可以看到最终数据并不是我们想要的结果,多线程同时操作volatile修饰变量,无法保证数据的原子性。

那如何解决这个问题呢,用sychornized,可以处理,但是这是重量级锁,不推荐使用,还可以用 AtomicInteger 来处理这个情况实现代码如下

 import java.util.concurrent.atomic.AtomicInteger;

 public class TestVolatile1 {

     public static void main(String[] args) {
MyData1 myData = new MyData1(); new Thread(new Runnable() {
@Override
public void run() {
//调用方法 赋值
myData.changeData();
for(int i = 0;i < 9999;i++) {
myData.changeData();
}
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"线程1").start(); new Thread(new Runnable() {
@Override
public void run() {
//调用方法 赋值
myData.changeData();
for(int i = 0;i < 9999;i++) {
myData.changeData();
}
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"线程2").start(); new Thread(new Runnable() {
@Override
public void run() {
//调用方法 赋值
myData.changeData();
for(int i = 0;i < 9999;i++) {
myData.changeData();
}
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"线程3").start(); new Thread(new Runnable() {
@Override
public void run() {
//调用方法 赋值
myData.changeData();
for(int i = 0;i < 9999;i++) {
myData.changeData();
}
System.out.println(Thread.currentThread().getName()+" : "+myData.data);
}
},"线程4").start(); while (Thread.activeCount() > 1) {
Thread.yield();
} System.out.println("最终数值 :"+myData.data);
}
} class MyData1{
AtomicInteger data = new AtomicInteger();
public void changeData(){
data.getAndIncrement();
} }

执行结果如下

如此数据原子性问题便解决了。

三 . 指令重排

在JVM在编译Java代码的时候,或者CPU在执行JVM字节码的时候,对指令顺序进行重新排序。在不改变程序执行结果的前提下,优化程序的运行效率(不改变单线程下的程序执行结果)

看一段简单的代码

 public class Data {

     int a = 1; //步骤1
int b = 2; //步骤2
int c = a+b; //步骤3 }

单线程下,代码执行结果c的结果3,但是在执行的过程时候并不一定是1 , 2,3这个执行顺序,在发生指令重排后,可能是2,1,3。单线程下对工程并没有什么影响。

但是如果是多线程,就会出现问题。查看如下方法

 public class Volatile {

     int a = 1;
boolean flag = false; public void dosome1() {
a = 2;// 步骤1
flag = true; //步骤2
} public void dosome2() {
if(flag){
int b = a+a; // 步骤3
}
}
}

上面的代码步骤3其实是两个步骤,为了好理解,可以看成为一个步骤。

如果线程A 操作dosome1 而线程而B 操作dosome2  如果不发生指令重排

可能顺序可能是 1,2,3    b=4   ,这也是我们期望的,

还会出现以下顺序

1,3,2      3,1,2  这两种可能性,如果是这两个,代表不符合条件,没有声明b变量。

但是如果发生重排后,因为1,2没有依赖关系,很有可能发生指令重排,那名执行的结果就可能出现以下顺序

2,3,1   如果出现这个顺序,就会声明变量b,结果为2;这个结果就会很恐怖了,就好比我们做了一个工程,每次执行的结果无法确定。这必然是不行的。为了解决这个问题,我们便可以用volatile来修饰变量。当然sychornized也可以解决。

重排是个比较麻烦的过程,这是一个简单理解,后续再做详细的探讨。

volatile的特性代码验证的更多相关文章

  1. volatile的特性

    volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这 ...

  2. Android中图像变换Matrix的原理、代码验证和应用(二)

    第二部分 代码验证 在第一部分中讲到的各种图像变换的验证代码如下,一共列出了10种情况.如果要验证其中的某一种情况,只需将相应的代码反注释即可.试验中用到的图片: 其尺寸为162 x 251. 每种变 ...

  3. ASP.NET 企业组织机构代码验证

    /// <summary> /// 组织机构代码验证 /// </summary> /// <param name="arg"></par ...

  4. 【NumberValidators】工商营业执照号码和统一社会信用代码验证

    从本质上讲,工商营业执照号码和统一社会信用代码是两套完全不一样的编码规则,识别结果也仅有行政区划部分为两者共有,但因为这两种编码同时存在的原因,所以如果需要在系统中唯一标志一家企业时,还是可以通过工商 ...

  5. ThreadLocal原理分析与代码验证

    ThreadLocal提供了线程安全的数据存储和访问方式,利用不带key的get和set方法,居然能做到线程之间隔离,非常神奇. 比如 ThreadLocal<String> thread ...

  6. Serializable详解(1):代码验证Java序列化与反序列化

    说明:本文为Serializable详解(1),最后两段内容在翻译上出现歧义(暂时未翻译),将在后续的Serializable(2)文中补充. 介绍:本文根据JDK英文文档翻译而成,本译文并非完全按照 ...

  7. 《神经网络的梯度推导与代码验证》之FNN(DNN)的前向传播和反向推导

    在<神经网络的梯度推导与代码验证>之数学基础篇:矩阵微分与求导中,我们总结了一些用于推导神经网络反向梯度求导的重要的数学技巧.此外,通过一个简单的demo,我们初步了解了使用矩阵求导来批量 ...

  8. 《神经网络的梯度推导与代码验证》之FNN(DNN)前向和反向过程的代码验证

    在<神经网络的梯度推导与代码验证>之FNN(DNN)的前向传播和反向梯度推导中,我们学习了FNN(DNN)的前向传播和反向梯度求导,但知识仍停留在纸面.本篇章将基于深度学习框架tensor ...

  9. 《神经网络的梯度推导与代码验证》之CNN的前向传播和反向梯度推导

    在FNN(DNN)的前向传播,反向梯度推导以及代码验证中,我们不仅总结了FNN(DNN)这种神经网络结构的前向传播和反向梯度求导公式,还通过tensorflow的自动求微分工具验证了其准确性.在本篇章 ...

随机推荐

  1. JVM系列之:详解java object对象在heap中的结构

    目录 简介 对象和其隐藏的秘密 Object对象头 数组对象头 整个对象的结构 简介 在之前的文章中,我们介绍了使用JOL这一神器来解析java类或者java实例在内存中占用的空间地址. 今天,我们会 ...

  2. 手动造轮子——为Ocelot集成Nacos注册中心

    前言     近期在看博客的时候或者在群里看聊天的时候,发现很多都提到了Ocelot网关的问题.我之前也研究过一点,网关本身是一种通用的解决方案,主要的工作就是拦截请求统一处理,比如认证.授权.熔断. ...

  3. xilinx fpga 生成3*3窗口

    在写滤波程序的时候在网上看了好几篇大佬的笔记,都有提到使用3*3窗口,由于小白一个,看到复杂的理论就惧怕的不行.但是现在不得不上,自己调用移位寄存器ip核然后做了个3*3窗口出来,自己动手作出来忽然感 ...

  4. u盘安装 osx 出现 “不能验证”

    关于安装是出现关于出现“不能验证”错误: 解决办法 :打开终端 在"终端"里面修改时间 输入:date 032208102015.20

  5. java 方法及引用数据类型

    一.方法 在java中,方法就是用来完成解决某件事情或实现某个功能的办法. 1.语法格式: 修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2,......){ 执行语句 ……… re ...

  6. 在excel中如何给一列数据批量加上双引号

    在实际开发中,会遇到这样的需求,大量的数据,需要从配置文件里读取,客户给到的枚举值是字符串,而配置文件里的数据,是json格式,需要加上双引号,这样就需要使用Excel来批量格式化一下数据. 客户给到 ...

  7. PHP is_callable() 函数

    is_callable() 函数用于检测函数在当前环境中是否可调用.高佣联盟 www.cgewang.com is_callable() 函数验证变量的内容能否作为函数调用. 这可以检查包含有效函数名 ...

  8. PHP xml_set_character_data_handler() 函数

    定义和用法 xml_set_character_data_handler() 函数为 XML 解析器建立字符数据处理器. 该函数规定当解析器在 XML 文件中找到字符数据时所调用的函数.高佣联盟 ww ...

  9. PHP substr_replace() 函数

    实例 把 "Hello" 替换成 "world": <?php高佣联盟 www.cgewang.comecho substr_replace(" ...

  10. PHP crypt() 函数

    定义和用法 crypt() 函数返回使用 DES.Blowfish 或 MD5 算法加密的字符串.高佣联盟 www.cgewang.com 在不同的操作系统上,该函数的行为不同,某些操作系统支持一种以 ...