volatile的特性代码验证
一 . 可见性(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的特性代码验证的更多相关文章
- volatile的特性
volatile的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这 ...
- Android中图像变换Matrix的原理、代码验证和应用(二)
第二部分 代码验证 在第一部分中讲到的各种图像变换的验证代码如下,一共列出了10种情况.如果要验证其中的某一种情况,只需将相应的代码反注释即可.试验中用到的图片: 其尺寸为162 x 251. 每种变 ...
- ASP.NET 企业组织机构代码验证
/// <summary> /// 组织机构代码验证 /// </summary> /// <param name="arg"></par ...
- 【NumberValidators】工商营业执照号码和统一社会信用代码验证
从本质上讲,工商营业执照号码和统一社会信用代码是两套完全不一样的编码规则,识别结果也仅有行政区划部分为两者共有,但因为这两种编码同时存在的原因,所以如果需要在系统中唯一标志一家企业时,还是可以通过工商 ...
- ThreadLocal原理分析与代码验证
ThreadLocal提供了线程安全的数据存储和访问方式,利用不带key的get和set方法,居然能做到线程之间隔离,非常神奇. 比如 ThreadLocal<String> thread ...
- Serializable详解(1):代码验证Java序列化与反序列化
说明:本文为Serializable详解(1),最后两段内容在翻译上出现歧义(暂时未翻译),将在后续的Serializable(2)文中补充. 介绍:本文根据JDK英文文档翻译而成,本译文并非完全按照 ...
- 《神经网络的梯度推导与代码验证》之FNN(DNN)的前向传播和反向推导
在<神经网络的梯度推导与代码验证>之数学基础篇:矩阵微分与求导中,我们总结了一些用于推导神经网络反向梯度求导的重要的数学技巧.此外,通过一个简单的demo,我们初步了解了使用矩阵求导来批量 ...
- 《神经网络的梯度推导与代码验证》之FNN(DNN)前向和反向过程的代码验证
在<神经网络的梯度推导与代码验证>之FNN(DNN)的前向传播和反向梯度推导中,我们学习了FNN(DNN)的前向传播和反向梯度求导,但知识仍停留在纸面.本篇章将基于深度学习框架tensor ...
- 《神经网络的梯度推导与代码验证》之CNN的前向传播和反向梯度推导
在FNN(DNN)的前向传播,反向梯度推导以及代码验证中,我们不仅总结了FNN(DNN)这种神经网络结构的前向传播和反向梯度求导公式,还通过tensorflow的自动求微分工具验证了其准确性.在本篇章 ...
随机推荐
- IDEA 编译 Jmeter 5.0
IDEA 编译 Jmeter 5.0 1.下载源码后解压,我这边下载的是最新的『apache-jmeter-5.0_src.tar』,解压. 2.解压后 修改下列两个文件 eclipse.classp ...
- Spark实现wordcount的几种方式
方法一:map + reduceByKey package com.cw.bigdata.spark.wordcount import org.apache.spark.rdd.RDD import ...
- 阿里云内部超全K8s实战手册!超全127页可下载
一直关注云计算领域的人,必定知道Docker和Kubernetes的崛起.如今,世界范围内的公有云巨头(谷歌.亚马逊.微软.华为云.阿里云等等)都在其传统的公共云服务之上提供托管的Kubernetes ...
- MySQL(一)简介与入门
一.数据库简介 这个博客详细介绍:http://www.cnblogs.com/progor/p/8729798.html 二.MySQL的安装 这个博客详细介绍:https://blog.csdn. ...
- 彻底解决ssh.invoke_shell() 返回的中文问题
上一篇:https://www.cnblogs.com/apff/p/9484939.html(python如何实现普通用户登录服务器后切换到root用户再执行命令遇到的错误解决 ) 接上一篇,前两篇 ...
- 友好城市dp
// // Created by Arc on 2020/4/27. //对了,这篇题解的代码是小白自己写的.有啥错误还请各位大佬多多包涵. /* * 某国有一条大河(一条大河~~~~,波浪宽~~~~ ...
- Django学习路17_聚合函数(Avg平均值,Count数量,Max最大,Min最小,Sum求和)基本使用
使用方法: 类名.objects.aggregate(聚合函数名('表的列名')) 聚合函数名: Avg 平均值 Count数量 Max 最大 Min 最小 Sum 求和 示例: Student.ob ...
- Seaborn基础3
import seaborn as sns import numpy as np import matplotlib.pyplot as plt sns.set(rc = {"figure. ...
- Blob分析之bottle.hdev
* 分割读取啤酒瓶子上的数字* bottle.hdev: Segment and read numbers on a beer bottle* 第0步* Step 0: Preparations*定义 ...
- C/C++编程笔记:C语言入门知识点(三),请收藏C语言最全笔记!
今天我们继续来学习C语言的入门知识点,第一课:C/C++编程笔记:C语言入门知识点(二),请收藏C语言最全笔记! 21. 输入 & 输出 当我们提到输入时,这意味着要向程序填充一些数据.输入可 ...