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的自动求微分工具验证了其准确性.在本篇章 ...
随机推荐
- Vue.js +pdf.js 处理响应pdf文件流数据,前端转图片预览不可下载
使用场景及原因 实际业务中,一些说明书或协议仅支持用户在线预览,为避免用户自行下载,并进行修改,引发纠纷,特将文件已文件流的形式,传给前端并转为图片显示,此时可能会有人问,为什么不直接在后端转图片,前 ...
- Netty 学习笔记(1) ------ Hello World
服务端启动流程 package com.example.netty; import com.example.netty.handler.HelloServerHandler; import io.ne ...
- 重学数据结构(三)——使用单链表实现LRU淘汰缓存机制
使用单链表实现LRU(Least Recently Used)淘汰缓存机制 需求:存在一个单链表,在单链表尾部的都是越早之前添加的元素. 当元素被访问到时,会添加进缓存(也就是这个单链表中). 如果这 ...
- hdu6755 Mow
半平面交+数组模拟双端队列 人生第一次代码过两百行啊...加油加油 #include<iostream> #include<algorithm> #include<cma ...
- vue.js全局组件和局部组件区别
在id=‘#app’,加入<pl>标签,但也访问不了局部chil组件: 在id=‘#div0’,加入<as>标签,但能访问到全局组件
- 1-Numpy的通用函数(ufunc)
一.numpy“通用函数”(ufunc)包括以下几种: 元素级函数(一元函数):对数组中的每个元素进行运算 数组级函数:统计函数,像聚合函数(例如:求和.求平均) 矩阵运算 随机生成函数 常用一元通用 ...
- PHP getrandmax() 函数
实例 返回通过调用 rand() 函数显示的随机数的最大可能值: <?phpecho(getrandmax()); ?>高佣联盟 www.cgewang.com 定义和用法 The get ...
- PHP strchr() 函数
实例 查找 "world" 在 "Hello world!" 中的第一次出现,并返回字符串的其余部分: <?php高佣联盟 www.cgewang.com ...
- luogu P4884 多少个1?
LINK:多少个1? 题目要求:\(\sum_{i=0}^{n-1}10^i \equiv k \mod m\) 最小的n. 看起来很难求的样子 这个同余式 看起来只能暴力枚举. 不过既然是同余 我们 ...
- 不用注入方式使用Spring管理的对象中的方法,神奇
在小冷工作中遇到这么一个小问题,当你的业务层对象交给spring管理之后,在普通的类中调用这个类中的方法时候,会有个问题这个类在调用时候会一直返回一个null,而且还会抛出一个空指针异常. 小冷在遇到 ...