一 . 可见性(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. .clearfix 清除浮动,@import

    我们知道,在网页的DIV+CSS布局中,很多时候要用到浮动. 既然有浮动,那就有清除浮动. 清除浮动有很多种方式,而在实际项目中,比较常用的是这一种. .clearfix:after { conten ...

  2. windows如何解决Error: Registry key 'Software\JavaSoft\Java Runtime Environment'\CurrentVersion'

    问题再现与解读 首次,使用 java 命令+任意参数 都会报这个错误,最典型的就是java -version 解读一下这个报错信息,注册表路径 Software\JavaSoft\Java Runti ...

  3. Java Web(2)-jQuery上

    一.jQuery初体验 使用jQuery给一个按钮绑定单击事件 <!DOCTYPE html> <html lang="en"> <head> ...

  4. js冒泡排序和数组去重

    1.冒泡排序 <script>//声明一个数组 var arr=[1,88,66,22,35,65,4,52];//自执行函数 (function maopao(){ for(var i= ...

  5. findViewByid一定要放在setContentView(R.layout.a..)之后

    findViewByid一定要放在setContentView(R.layout.a..)之后否则还没布局,根本找不到这些控件setContentView(R.layout.activity_inpu ...

  6. 网络通信机制:Socket、TCP/IP、HTTP

    13.1.1 TCP/IP协议 讲的很抽象,没具体看懂什么是TCP协议,什么是IP协议.IP协议保证消息从一个主机传送到另一个主机,消息在传送的过程中被分割成一个个小包,TCP协议会让两台相互连接的计 ...

  7. Java Web(5)-Servlet详解(下)

    一.HttpServletRequest 类 1. HttpServletRequest 类作用? 每次只要有请求进入 Tomcat 服务器,Tomcat 服务器就会把请求过来的 HTTP 协议信息解 ...

  8. Mybatis-Plus中Wrapper的方法

    public interface EntityService extends IService<TbEntity>{ }entityService.update(entity,Condit ...

  9. matplotlib基础汇总_04

    3D图形 导包 import numpy as np import matplotlib.pyplot as plt #3d图形必须的 from mpl_toolkits.mplot3d.axes3d ...

  10. PHP array_merge_recursive() 函数

    实例 把两个数组合并为一个数组: <?php$a1=array("a"=>"red","b"=>"green&q ...