Java volatile关键字特性讲解上篇

 更新时间:2022年12月12日 15:52:09   作者:爱吃南瓜糕的北络  
JMM要求保证可见性、原子性、有序性,volatile可以保证其中的两个,本篇文章具体验证volatile的可见性,不原子性和禁重排,同时解决volatile的不保证原子性,让代码具有原子性

一、概述

volatile是Java中的关键字,用来修饰会被不同线程访问和修改的变量。

volatile是Java虚拟机提供的轻量级的同步机制,它有三个特性:

(1)保证可见性

(2)不保证原子性

(3)禁止指令重排

二、特性详解

volatile保证可见性

Java内存模型(JMM)定义了一组规则、规范,规定了程序中各个变量的访问方法。JMM关于同步的规定:

(1)线程解锁前,必须把共享变量的值刷新回主内存;

(2)线程加锁前,必须读取主内存的最新值同步到自己的工作内存;

(3)加锁解锁必须是同一把锁;

说明:由于JVM运行程序的实体是线程,创建每个线程时,JMM会为其创建一个工作内存(也称栈空间),工作内存是每个线程的私有数据区域。

Java内存模型规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问。

但是线程对变量的操作(读取、赋值等)必须在工作内存中进行。因此首先要将变量从主内存拷贝到自己的工作内存,然后对变量进行操作,操作完成后再将变量写会主内存中。

举例说明:

(1)火车票卖票系统还剩下一张票,并已经刷入到主内存中:ticketNum = 1;

(2)此时有3个用户在同时购买票,3个线程都读入了目前的票数,ticketNum=1,那么线程就会继续进入购买流程。

(3)假设其中一个线程先抢占了CPU资源,先买到票,并将自己的工作内存中的ticketNum值改为0,ticketNum=0,然后再写回到主内存。

这时,由于一个线程的用户已经买到了票,那么其他用户的线程应该不能再继续进入购买票的流程了,因此需要系统通知到其他线程 ticketNum=0 这个消息。如果可以达到这样的效果,可以理解为 具有可见性。

无可见性代码演示:

@Test
public void test1() {
    DataDemo dataDemo = new DataDemo();
    RunThread runThread = new RunThread(dataDemo);
    runThread.start();
    while (dataDemo.getNumber() == 0) {
    }
    System.out.println("具有可见性验证通过");
}
public class DataDemo {
    private int number = 0;
    public void add() {
        this.number = this.number + 10;
    }
    public int getNumber() {
        return number;
    }
}
public class RunThread extends Thread {
    private DataDemo dataDemo;
    public RunThread(DataDemo dataDemo) {
        this.dataDemo = dataDemo;
    }
    @Override
    public void run() {
        System.out.println("线程[" + Thread.currentThread().getName() + "] 正在执行");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        dataDemo.add();
        System.out.println("线程[" + Thread.currentThread().getName() + "]更新后,number值为:" + dataDemo.getNumber());
    }
}

执行结果:
线程[Thread-0] 正在执行
线程[Thread-0]更新后,number值为:10

结果分析:

可以看出子线程启动后将number值改为了10,虽然已经改为了非0,但是主线程仍然一直处于while循环中,因此此时number不具有可见性,系统不会主动通知主线程number值修改。

原理说明:

这个问题其实就是私有堆栈中的值和公共堆栈中的值不同步造成的。解决这样的问题就要使用 volatile 关键字了,它主要的作用就是当线程访问number这个变量时,强制性从公共堆栈中进行取值。

可见性代码演示:

@Test
public void test1() {
    DataDemo dataDemo = new DataDemo();
    RunThread runThread = new RunThread(dataDemo);
    runThread.start();
    while (dataDemo.getNumber() == 0) {
    }
    System.out.println("具有可见性验证通过");
}
public class DataDemo {
    // 给变量 number 添加 volatile 关键字修饰
    volatile private int number = 0;
    public void add() {
        this.number = this.number + 10;
    }
    public int getNumber() {
        return number;
    }
}
public class RunThread extends Thread {
    private DataDemo dataDemo;
    public RunThread(DataDemo dataDemo) {
        this.dataDemo = dataDemo;
    }
    @Override
    public void run() {
        System.out.println("线程[" + Thread.currentThread().getName() + "] 正在执行");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        dataDemo.add();
        System.out.println("线程[" + Thread.currentThread().getName() + "]更新后,number值为:" + dataDemo.getNumber());
    }
}

执行结果:
线程[Thread-0] 正在执行
线程[Thread-0]更新后,number值为:10
具有可见性验证通过

结果分析:

通过对变量number变量添加了volatile关键字修饰,可以看出子线程启动后将number值改为了10,随后主线程跳出了while循环,输出了“具有可见性验证通过”,说明此时number具有可见性。

原理说明:

通过使用 volatile 关键字,强制从公共内存中读取变量的值,内存结构如图:

到此这篇关于Java volatile关键字特性讲解上篇的文章就介绍到这了,更多相关Java volatile内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java中的转换流InputStreamReader解读

    Java中的转换流InputStreamReader解读

    InputStreamReader是Java.io包中的一个类,用于将字节输入流转换为字符输入流,它继承自java.io.Reader类,提供了两种构造方法,可以使用默认或指定字符集创建实例,常用方法包括读取字符、判断是否准备好读取数据和关闭流
    2024-09-09
  • Java中Integer的parseInt和valueOf的区别详解

    Java中Integer的parseInt和valueOf的区别详解

    这篇文章主要介绍了Java中Integer的parseInt和valueOf的区别详解,nteger.parseInt(s)是把字符串解析成int基本类型,Integer.valueOf(s)是把字符串解析成Integer对象类型,其实int就是Integer解包装,Integer就是int的包装,需要的朋友可以参考下
    2023-11-11
  • SpringBoot Security使用MySQL实现验证与权限管理

    SpringBoot Security使用MySQL实现验证与权限管理

    安全管理是软件系统必不可少的的功能。根据经典的“墨菲定律”——凡是可能,总会发生。如果系统存在安全隐患,最终必然会出现问题,这篇文章主要介绍了SpringBoot安全管理Spring Security基本配置
    2022-11-11
  • 超详细讲解SpringCloud Commons公共抽象的用法

    超详细讲解SpringCloud Commons公共抽象的用法

    这篇文章主要介绍了超详细讲解SpringCloud Commons公共抽象的用法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • Java不用算数运算符来实现求和方法

    Java不用算数运算符来实现求和方法

    我们都知道,Java的运算符除了具有优先级之外,还有一个结合性的特点。当一个表达式中出现多种运算符时,执行的先后顺序不仅要遵守运算符优先级别的规定,还要受运算符结合性的约束,以便确定是自左向右进行运算还是自右向左进行运算,但是如果不用运算符怎么求和呢
    2022-04-04
  • mybatis-plus支持null字段全量更新的两种方法

    mybatis-plus支持null字段全量更新的两种方法

    本文主要介绍了mybatis-plus支持null字段全量更新的两种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • 文件上传SpringBoot后端MultipartFile参数报空问题的解决办法

    文件上传SpringBoot后端MultipartFile参数报空问题的解决办法

    这篇文章主要介绍了文件上传SpringBoot后端MultipartFile参数报空问题的解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • SpringBoot通过注解注入Bean的几种方式解析

    SpringBoot通过注解注入Bean的几种方式解析

    这篇文章主要为大家介绍了SpringBoot注入Bean的几种方式示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-03-03
  • springboot结合mysql主从来实现读写分离的方法示例

    springboot结合mysql主从来实现读写分离的方法示例

    这篇文章主要介绍了springboot结合mysql主从来实现读写分离的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • Java读取并下载网络文件的方法

    Java读取并下载网络文件的方法

    这篇文章主要为大家详细介绍了Java读取并下载网络文件的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08

最新评论