一 . 可见性(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. 【Docker】Redis 安装使用教程

    1.安装 1.1 拉取镜像 docker pull redis redis:4.0 1.2 创建redis容器名"redistest1",并开启持久化 docker run -d ...

  2. Bing每日壁纸API

    懒人直接出图 https://www.shadow-forum.com/api/bing/bing.php API API地址: https://bing.biturl.top 调用方式: HTTP ...

  3. java消除整型数组中重复的元素,排序后输出新数组

    法一: import java.util.Arrays; import java.util.Scanner; public class Main { public static void main(S ...

  4. CCNA - Part10 数据包的通信过程

    这篇文章主要是对数据包在同网段和不同网段的转发流程梳理,使用 ping 命令进行实际抓包测试. 网关的概念: 对于像 PC 等终端设备来说,通过交换机可以实现同网段的通信.但如果想要给其他网段发生数据 ...

  5. 题解 洛谷 P4694 【[PA2013]Raper】

    首先考虑题目的性质,不难发现光盘的花费是一个凸函数.当生产 \(0\) 张光盘时,其花费为 \(0\),随着光盘生产数的增加,最优情况肯定是先选择工厂便宜的时刻,所以花费会增长越来越快,因此其为一个下 ...

  6. SQL Server数据类型对应.Net Core中的数据类型

    SQL C# bigint(sql大小:8byte) long(64位) int, integer(sql大小:4byte) int(32位) smallint(sql大小:2byte) short( ...

  7. 云原生时代高性能Java框架—Quarkus(二)

    --- *构建Quarkus本地镜像.容器化部署Quarkus项目* Quarkus系列博文 Quarkus&GraalVM介绍.创建并启动第一个项目 构建Quarkus本地镜像.容器化部署Q ...

  8. UWP 自定义密码框控件

    1. 概述 微软官方有提供自己的密码控件,但是控件默认的行为是输入密码,会立即显示掩码,比如 *.如果像查看真实的文本,需要按查看按钮. 而我现在自定义的密码控件是先显示你输入的字符2s,然后再显示成 ...

  9. seaborn线性关系数据可视化:时间线图|热图|结构化图表可视化

    一.线性关系数据可视化lmplot( ) 表示对所统计的数据做散点图,并拟合一个一元线性回归关系. lmplot(x, y, data, hue=None, col=None, row=None, p ...

  10. python 连接 mysql 的三种驱动

    连接 mysql 驱动 mysq1-client python2,3都能直接使用 对myaq1安装有要求,必须指定位置存在 配置文件 python-mysql python3 不支持 pymysql ...