Volatile的学习

首先先介绍三个性质

可见性

可见性代表主内存中变量更新,线程中可以及时获得最新的值。

下面例子证明了线程中可见性的问题

由于发现多次执行都要到主内存中取变量,所以会将变量缓存到线程的工作内存,这样当其他线程更新该变量的时候,该线程无法得知,导致该线程会无限的运行下去。

public class test1 {
    private static int flag = 1;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            while (flag == 1){

            }
        },"t1");
        t1.start();
        Thread.sleep(1000);
        flag = 2;
    }
}

当我们在这个死循环中加入一个synchronized关键字的话就会将更新
猜测:synchronized会使更新当前线程的工作内存

public class test1 {
    private static int flag = 1;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            while (flag == 1){
                synchronized ("1"){

                }
            }
        },"t1");
        t1.start();
        Thread.sleep(1000);
        flag = 2;
    }
}

原子性

即多线程中指令执行会出现交错,导致数据读取错误。

比如i++的操作就可以在字节码的层面可以被看成以下操作

9 getstatic #9 <com zhf test3 test2.i : i>   &#x83B7;&#x5F97;i
12 iconst_1    &#x5C06;1&#x538B;&#x5165;&#x64CD;&#x4F5C;&#x6570;&#x6808;
13 isub   &#x5C06;&#x4E24;&#x6570;&#x76F8;&#x51CF;
14 putstatic #9 <com zhf test3 test2.i : i>  &#x5C06;i&#x53D8;&#x91CF;&#x5B58;&#x50A8;
</com></com>

然后在多线程的情况下,会出现以下程序出现非0的结果。

public class test2 {

    private static int i;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            for (int j = 0; j < 400; j++) {
                i++;
            }
        });
        Thread t2 = new Thread(()->{
            for (int j = 0; j < 400; j++) {
                i--;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
@Slf4j
public class test3 {

    private Thread monitor;
    private volatile boolean flag = false;

    public static void main(String[] args) {
        test3 test3 = new test3();

        test3.monitor = new Thread(()->{
            while (true){
                Thread thread = Thread.currentThread();
                if (test3.flag){
                    log.debug("正在执行后续");
                    break;
                }
                try {
                    log.debug("线程正在执行");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 当进程在睡眠过程中被Interrupte()打断此时isInterrupted()为false
                    // 从而当异常被抓住后会继续执行
                    // 所以要调用下面方法继续将isInterrupted()置为true
                    // thread.interrupt();
                }
            }
        });

        test3.start();
        try {
            Thread.sleep(5500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        test3.stop();
    }
    public void stop(){
        flag = true;
        monitor.interrupt();
    }

    public void start(){
        monitor.start();
    }
}

具体实现,最常见的就是单例模式。

首先是饿汉模式,这里的多线程安全是由JVM保证的,对象是在类加载的加载阶段创建的。

class SingtonHungry{
    private static Object object = new Object();

    // &#x997F;&#x6C49;&#x6A21;&#x5F0F;
    public synchronized Object getObject() {
        return object;
    }
}

其次就是饿汉模式,最常见的不过就是下面的进行多线程安全的方案。文章后面会对其进行优化。

class SingtonLazy{
    private Object object;

    // 懒汉模式
    // 由于这样的话不管有没有创建出对象都要加锁然后才能取对象,性能太差
    public synchronized Object getObject() {
        if (object == null){
            object = new Object();
            return object;
        }
        return object;
    }
}

有序性

JVM会对指令进行重排序,其和CPU的流水线操作类似,当需要流水线操作的时候,需要进行优化的时候,就会对CPU指令进行重排序优化。

当操作的顺序变了之后,就会出现问题。可能会导致条件的提前触发等等。

Volatile使用

使用域: Volatile只能在类的静态成员变量或者成员变量上。

volatile标识符能够让线程强制去读主存的该变量的值,保证了线程变量的可见性。

volatile标识符能够让线程去顺序执行该变量的操作,保证了执行变量的语句的有序性

  • 在读取该变量时,会为其添加读屏障。在该读屏障之后的代码不会放在读屏障之前执行。
  • 在写该变量时,会为其添加写屏障。在该写屏障之前的代码不会在屏障之后执行。

所以在volatile的修饰下,能够保证变量的可见性和有序性,但并不能保证其的原子性。

class SingtonLazy{
    // 加上volatile的主要目的就是防止在synchronized内的代码指令重排,正常是先构造好对象然后赋对象地址
    // 导致object会被首先赋予了地址,导致其不为null,然而构造方法还没有开始构造
    // 被其他的线程拿走会出现使用出错。
    private static volatile Object object;

    // 懒汉模式
    public static  Object getObject() {
        if (object != null){
            return object;
        }else{
            // 这里可能出现这里的线程还没有为其进行声明对象,但已经由线程进入了等待锁
            // 所以需要在这里来一个为空判断。
            synchronized (SingtonLazy.class){
                // 这里可能会出现指令重排,所以要加上volatile
                if(object == null){
                    object = new Object();
                }
                return object;
            }
        }
    }
}

实现单例的另外一个方式

public class Singleton {
    // 当使用到ObjectHolder才会进行到这个静态内部类的加载,同时才会创建该类
    // 也是属于懒汉式
    private static class ObjectHolder{
        static final Singleton singleton = new Singleton();
    }

}

synchronized补充

首先在synchronized代码块中,它会保证代码块中的可见性,原子性和有序性。

有序性仅仅是表现在synchronized的执行后最后的结果都是一样的,并不会阻止JVM在其内部进行代码的重排序。就比如上个例子来说,在synchronized代码块中最后代码的执行结果都是一样的,但可能由于其优化,导致其他线程出错。

Original: https://www.cnblogs.com/duizhangz/p/16243965.html
Author: 大队长11
Title: Volatile的学习

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/621794/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • java_异常机制(二)

    1.异常对象处理完之后会怎样: 异常处理对象在异常处理完后,没有引用指向它,变成了不可达对象,Exception对象会在下一个垃圾回收过程中被回收掉。 它将在接下来JVM进行gc操…

    Java 2023年6月5日
    073
  • Exception in thread “main” java.lang.StackOverflowError at java.util.ArrayList$SubList.rangeCheckForAdd(Unknown Source)

    Exception in thread “main” java.lang.StackOverflowError at java.util.ArrayList…

    Java 2023年5月29日
    068
  • java 异常类与自定义异常

    目录 异常类 Exception 类的层次 throws/throw 关键字: throws: throw: try catch finally语句 声明自定义异常 异常类 在 J…

    Java 2023年6月9日
    093
  • spring boot 支持返回 xml

    JAXB(Java Architecture for XML Binding) 是一个业界的标准,可以实现java类和xml的互转 jdk中包括JAXB JAXB vs jacks…

    Java 2023年5月30日
    064
  • springboot分析——自定义启动类

    在实际开发过程中,如果有一些公共功能,我们可以单独封装,然后配置成starter启动类,其他的项目需要使用时,主要 只要依赖开启就可以了。下面我们自定义一个自动配置启动类。 一:自…

    Java 2023年5月30日
    064
  • 日常踩坑_jpa的踩坑心得

    背景提要 使用jpa的出现了很多问题1、使用between做日期范围查询时报错2、使用@Query注解写原生sql时报错3、使用@where注解自动在sql后添加条件时查不出东西4…

    Java 2023年6月7日
    080
  • web上线部署系统 Walle

    Walle瓦力是基于git和rsync实现的一个web部署系统工具。 用户分身份注册、登录 开发者发起上线任务申请 管理者审核上线任务 支持多项目部署 快速回滚 部署前准备任务(前…

    Java 2023年6月8日
    073
  • SpringBoot 2.7.0 处理跨域的问题

    package com.clickpaas.config; import org.springframework.context.annotation.Bean; import o…

    Java 2023年6月8日
    093
  • 【HarmonyOS 】【JAVA UI】HarmonyOS 加载网络图片

    ​ 主要作用 加载网络图片功用于界面显示 参考资料 权限开发指导 线程管理 图像开发概述 代码实现 config.json配置 config.json代码如下 "reqP…

    Java 2023年5月29日
    069
  • Ubuntu 20.04 挂载 NTFS 硬盘

    创建挂载目录 mkdir /mnt/hdd 此目录在最后一步中用得到。 确定要挂载的硬盘 fdisk -l 由于我们要挂载的是 NTFS 硬盘,根据上面的信息,可以确定 /dev/…

    Java 2023年6月7日
    077
  • springboot竟然有5种默认的加载路径,你未必都知道

    上次分享了如何一步一步搭建一个springboot的项目,详细参见《5分钟快速搭建一个springboot的项目》,最终的结果是在”8080″端口搭建起了服…

    Java 2023年6月9日
    082
  • Java JDK和IntelliJ IDEA 配置及安装

    序言 初学java,idea走一波先。安装完成,配置配置项. idea 软件 官方下载地址:https://www.jetbrains.com/idea/download/#sec…

    Java 2023年5月29日
    067
  • SpringBoot注解@NotNull,@NotBlank,@Valid自动判定空值

    搭建springboot项目,我们都是采用的Restful接口,那么问题来了,当前端调用接口或者是其他项目调用时,我们不能单一靠调用方来控制参数的准确性,自己也要对一些非空的值进行…

    Java 2023年6月5日
    070
  • React三大属性

    最近学习了一波react,暂时感觉用起来很舒服,和vue相比,react最大的特点就是需要有点js的基础,不然有点难搞! react既然用起来这么舒服,这次就说说react不得不聊…

    Java 2023年6月6日
    055
  • 如何基于Security实现OIDC单点登录?

    一、说明 本文主要是给大家介绍 OIDC 的核心概念以及如何通过对 Spring Security 的授权码模式进行扩展来实现 OIDC 的单点登录。 OIDC 是 OpenID …

    Java 2023年6月6日
    079
  • 第一次真正使用泛型

    背景 最近在做一个产品的版本设计功能,多个模块均涉及到版本管理,一开始着急上线,实现方式上是先完成一个模块的版本管理,把链路调通,然后上线。等到写其他模块的版本管理的时候,发现代码…

    Java 2023年6月13日
    071
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球