当前位置 : 主页 > 编程语言 > java >

Java关键字-volatile详解

来源:互联网 收集:自由互联 发布时间:2023-03-22
点击​​Mr.绵羊的知识星球​​解锁更多优质文章。 一、介绍 1. 简介 volatile是java关键字,同时也是JVM 提供的轻量级的同步机制 2. 特性 你需要先了解一下Java内存模型Java Memory Model (J

点击  ​​Mr.绵羊的知识星球​​ 解锁更多优质文章。

一、介绍

1. 简介

    volatile是java关键字,同时也是JVM 提供的轻量级的同步机制

Java关键字-volatile详解_java

2. 特性

    你需要先了解一下Java内存模型Java Memory Model (JMM详解,写完上传),而volatile关键字拥有以下特性(不保证原子性),也就是说他无法保证线程安全。

    (1) 保证可见性:git地址

    可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。

import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * VolatileVisibility * 保证可见性验证 * * @author wxy * @date 2023-02-23 */public class VolatileVisibility extends AbstractVolatile { private static final Logger LOGGER = LoggerFactory.getLogger(VolatileVisibility.class); public static void main(String[] args) { // 验证可见性 visibility(); } public static void visibility() { Count count = new Count(); // 将num初始值设置为0 count.num = 0; new Thread(() -> { // 线程1休眠3秒 sleep(3000); // 休眠3秒后, 将num + 1 count.add(1); }, "thread 1").start(); new Thread(() -> { // 循环 while (true) { if (count.getNum() == 1) { // 如果线程2获取到线程1修改完成的num, 则打印后跳出while循环 LOGGER.info("current num value: {}", count.getNum()); break; } } }, "thread 2").start(); // main线程等待5秒后结束, 你也可以使用其他的方式等待上面线程执行完毕。例如CountdownLatch...之后文章都会介绍 sleep(5000); LOGGER.info("thread main end"); }}

    a. 未加volatile关键字

    上述代码中线程2先获取num=0的值,由于不满足if (count.getNum() == 1)的条件,所以无法跳出循环。三秒钟之后线程1将num的值修改为1,但是线程2无法感知将一直处于循环状态,导致程序无法停止。

Java关键字-volatile详解_volatile_02

    b. 加了volatile关键字

    上述代码中线程2先获取num=0的值,由于不满足iif (count.getNum() == 1)的条件,所以处于循环状态。三秒钟之后线程1将num的值修改为1,因为num被volatile修饰,具有可见性,num=1的修改对线程2可见,满足条件,所以线程2跳出循环,程序停止。

Java关键字-volatile详解_可见性_03

    (2) 不保证原子性:git地址

    无法保证变量同时只能被一个线程修改,也就是无法保证线程安全。(代码可以不看,就是多个线程操作同一个共享变量)

import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * VolatileNonAtomicity * 不保证原子性验证 * * @author wxy * @date 2023-02-23 */public class VolatileNonAtomicity extends AbstractVolatile { private static final Logger LOGGER = LoggerFactory.getLogger(VolatileNonAtomicity.class); public static void main(String[] args) { // 验证不保证原子性 nonAtomicity(); } public static void nonAtomicity() { Count count = new Count(); for (int num = 0; num < 10; num++) { // 创建10个线程 new Thread(() -> { for (int index = 0; index < 20; index++) { // 循环十次, 每次循环+1 count.add(1); // 休眠1毫秒(我电脑性能太好, 不休眠不好看结果) sleep(1); } }).start(); } // main线程休眠3秒等待上面执行完毕, 你也可以使用其他的方式等待上面线程执行完毕。例如CountdownLatch...之后文章都会介绍 sleep(3000); LOGGER.info("current num value: {}", count.getNum()); }}

    循环了200次,实际结果应该是200,但是最后结果是175。

Java关键字-volatile详解_不保证原子性_04

    (3) 禁止指令重排序:git地址

    计算机在执行程序时,为了提高性能,编译器和处理器通常都会对指令做重排序。这会产生什么样的问题呢?

    多线程环境下,由于编译器优化重排,导致不同线程中使用的变量不能保证一致性,无法确定执行的结果。volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象。

import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * VolatileInstructReset * 禁止指令重排序验证 * * @author wxy * @date 2023-02-23 */public class VolatileInstructReset extends AbstractVolatile { private static final Logger LOGGER = LoggerFactory.getLogger(VolatileInstructReset.class); /* 未使用volatile关键字前: n次循环后a,b同时为0 */ private static int a; private static int b; private static int x; private static int y; /* 增加volatile关键字后: a,b都符合不同时为0的预期 */ /*private static volatile int a; private static volatile int b; private static volatile int x; private static volatile int y;*/ public static void main(String[] args) throws InterruptedException { // 指令重排 instructReset(); } public static void instructReset() throws InterruptedException { long startTime = System.currentTimeMillis(); for (; ; ) { /* 定义四个数组, 每个里面仅有一个元素并且初始值为0 */ a = 0; b = 0; x = 0; y = 0; Thread thread1 = new Thread(() -> { // 将x赋值为1, 执行后x=1 x = 1; // 将a赋值为y, 如果线程1先执行那么a=0, 如果线程2先执行那么a=1 a = y; }, "线程1"); Thread thread2 = new Thread(() -> { // 将y赋值为1, 执行后y=1 y = 1; // 将b赋值为x, 如果线程1先执行那么b=1, 如果线程2先执行那么b=0 b = x; }, "线程2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); if (a == 0 && b == 0) { // 理论上不管是先执行线程1还是先执行线程2, a[0] & b[0]不可能同时为0 // 实际上确有同时为0的可能 LOGGER.info("a: {}, b: {}", a, b); break; } } LOGGER.info("execute end, cost time: {}", System.currentTimeMillis() - startTime); }}

    a. 未加volatile关键字

    按照代码中的逻辑,a和b不可能同时为0。但是在多线程下,a和b竟然同时为0,说明在多线程环境下,指令重排使得变量执行或赋值顺序被修改。

Java关键字-volatile详解_java_05

    b. 加了volatile关键字

    测试了两次,每次大概四十分钟(手动停了),a和b都不同时为0,说明多线程环境下,使用volatile关键字修饰后,执行或赋值顺序没有被修改。

Java关键字-volatile详解_不保证原子性_06

二、实际应用

1. 案例一

    ​​Java单例模式(懒汉式)​​

上一篇:布隆过滤器
下一篇:没有了
网友评论