前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >谈谈你对volatile的理解

谈谈你对volatile的理解

作者头像
名字是乱打的
发布2022-05-13 12:43:24
4350
发布2022-05-13 12:43:24
举报
文章被收录于专栏:软件工程
1,volatile是java虚拟机提供的轻量级弱同步机制(轻量级Synchroniza)它有三大特性
  • 保证可见性
  • 不保证原子性
  • 禁止指令重排
保证可见性即遵从JMM的可见性(详情见我写的JMM).

volatile保证可见性的代码演示:

image.png

代码语言:javascript
复制
demo2
package jucTest;

public class VolatileTest {

     boolean  stop = false;//如果不加关键字,程序将一直循环
//  volatile boolean stop = false;  

    public static void main(String[] args) throws Exception{
        VolatileTest v = new VolatileTest();
        Thread ta = new Thread(()->v.execute());
        ta.start();
        Thread.sleep(2000);
        Thread tb = new Thread(()->v.shutdown());
        tb.start();
    }

    public void execute(){
        while(!stop){
            String a = "a";
//          System.out.print("");
        }
    }
    public void shutdown(){
        System.out.println("do stop");
        stop = true;
    }



}
volatile不保证原子性怎么办
什么是原子性(数据的完整性和不可分割性,不可加塞处理),什么情况会失去原子性?

因为在多线程中,ABC 3个线程拿到主内存的数据s后,可能出现,A改了s的值正要刷回主内存的时候线程被挂起,这时候B线程改了s的值,当A线程再次开启时候还没来得及被通知就已经把自己改后的数据注入了,这时候就存在一个数据的丢失问题.

如何在不使用synchroniza的情况下保证int类数据的原子性呢? java.until.concurrent.atomic.AtomcInteger,它提供了一个保证原子性的int类的数据类AtomicInteger,它可以保证数据的原子性,可以当作int值来使用,自身带有操作数方法 如:AtomicInteger ai=new AtomicInteger(4); ai就是一个值为4的数据,如果括号内不写的话 默认为0

volatile的禁止指令重排

指令重排: 在计算机执行指令的顺序在经过程序编译器编译之后形成一个指令序列,一般而言,这个指令序列是会输出确定的结果;以确保每一次的执行都有确定的结果。但是,一般情况下,CPU和编译器为了提升程序执行的效率,会按照一定的规则允许进行指令优化,在某些情况下,这种优化会带来一些执行的逻辑问题,主要的原因是代码逻辑之间是存在一定的先后顺序,在并发执行情况下,会发生二义性,即按照不同的执行逻辑,会得到不同的结果信息。

image.png

volatile禁止了指令重排优化,从而避免了多线程环境下程序出现乱序执行的现象

为什么volatile可以做到保证可见性和禁止指令重排?

先了解一个概念,内存屏障(Memory Barier)又称内存栅栏,是一个CPU指令,内存屏障可以禁止特定类型处理器的重排序,从而让程序按我们预想的流程去执行。内存屏障,又称内存栅栏,是一个CPU指令,基本上它是一条这样的指令:

  • 保证特定操作的执行顺序
  • 影响某些数据(或则是某条指令的执行结果)的内存可见性

编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。 而插入一条Memory Barrier会告诉编译器和CPU: ①不管什么指令都不能和这条Memory Barrier指令重排序。 ②Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。

volatile就是基于Memory Barier 如果一个变量是volatile修饰的,JMM会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。这意味着,如果写入一个volatile变量,内存屏障的插入就可以保证:

  • 一个线程写入变量a后,任何线程访问该变量都会拿到最新值。
  • 在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。

volatile的读写屏障图

image.png

如何使线程的安全性得到保障
代码语言:javascript
复制
工作内存与主内存同步延迟现象导致的可见性问题
可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。

对于指令重排导致的可见性问题和有序性问题
可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。

关于 JMM指令集

代码语言:javascript
复制
read (读取) 作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
load (载入) 作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use (使用) 作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值得字节码指令时就会执行这个操作。
assign (赋值) 作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store (存储) 作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后write操作使用。
lock (锁定) 作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
unlock (解锁) 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
write (写入) 作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-05-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 关于 JMM指令集
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档