Java并发机制的底层实现之volatile关键字

1. volatile关键字

1.1. volatile的作用

volatile具有可见性、有序性,其保证不了原子性。下面我们对其可见性和原子性进行说明,有序性后面再说。

原子性:volatile修饰的变量,其运算并不具备原子性,对其进行多线程操作时,是不安全的。

java 复制代码
import java.util.concurrent.TimeUnit;

class Number {
    volatile int number = 0;
}

public class AtomicTest01 {
    public static void main(String[] args) throws InterruptedException {
        Number myNumber = new Number();

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    myNumber.number++;
                }
            }).start();
        }

        TimeUnit.SECONDS.sleep(5);
        System.out.println(myNumber.number);
    }

}

这段代码的运行结果并不总是10000,经常会是小于10000的结果。因为number++运算,要经历多条CPU指令,如加载number的原始值到寄存器、为原始值加1、将运算结果写会CPU缓存,在多线程的情况下,线程A可能执行到为原始值加1这条指令了,线程B可能正好在执行加载number的原始值到寄存器,这样线程B加载的就是线程A还没有进行加1的值,这样最终的计算结果就会比预想的值要小。

综上可以看出,基础类型被volatile关键字修饰后,其运算也是不具备原子性的。

可见性:出现可见性问题的原因是现在进入CPU多核时代,另外为了弥补CPU与内存之间的性能差异,在每个CPU核心都设置了多级缓存,CPU在代码的过程中,会将对一些变量的修改,先放到CPU缓存中,并不立即同步到主内存。这对于代码中的局部变量还好,在每个线程中的局部变量是互不影响的。对于共享变量,就会存在比较大问题,线程A对共享变量的修改一开始只会将修改结果同步到CPU缓存中,没有写回主内存,这样线程B在读取这个共享变量的时候,就会读取到以前的旧值。

这种情况下,可以使用volatile关键字对共享变量进行修饰,这样对共享变量进行修改时,就会立即同步到主内存中,另外其他线程在读取volatile关键字修饰的共享变量时,也会主动去主内存中去查找最新的值。

java 复制代码
import java.util.concurrent.TimeUnit;

public class VisibleTest01 {

    volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = Thread.ofVirtual().name("t1_thread").start(() -> {
            while (flag) {
                for (int i = 0; i < 1000; i++) {
                    i = i + 1;
                }
            }

            System.out.println(Thread.currentThread().getName() + " stop runnuing");
        });

        TimeUnit.MILLISECONDS.sleep(2000);

        Thread t2 = Thread.ofVirtual().name("t2_thread").start(() -> {
            flag = false;
        });

        t1.join();
        t2.join();
    }

}

上面代码中,flag如果不使用volatile修饰的话,t1线程将长时间执行while循环,因为它一直捕获不到flag已经被设置为true了。

像System.out.println()(该方法中有Lock和synchronized代码块,进入这些代码块的时候,会刷新线程的CPU缓存区域)、Thread.sleep(n)等语句,很多都有类似volatile的可见性效果,如果while循环中有类似语句,也达不到上面代码的演示效果,即使flag不用volatile修饰,t1线程也不会长时间在while循环中。

相关推荐
程序猿_极客13 分钟前
【免费】分享一套优质的基于SSM的校园失物招领系统的设计与实现,源码+文档+视频详解(讲解)
java·mysql·ssm·课程设计·失物招领系统
白远山1 小时前
上海24小时自助健身房解决方案实战指南与经验分享
java·数据库·架构·需求分析
码兄科技1 小时前
24小时自助健身房系统软件开发实战指南:功能设计与部署方案
java·spring boot·uni-app
程序猿_极客1 小时前
【免费】分享一套优质的基于SpringBoot的扶贫助农管理系统(详解)
java·springboot·课程设计·计算机项目
2603_965148111 小时前
AI+API选品:下一代智能商务助手雏形已现
java·大数据·数据库·人工智能·数据挖掘
wuminyu1 小时前
HotSpot的轻量锁与膨胀后的ObjectMonitor状态重建原理
java·linux·c语言·jvm·c++
CoderYanger1 小时前
A.每日一题:835. 图像重叠
java·开发语言·程序人生·leetcode·面试·职场和发展·学习方法
北冥有鱼被烹1 小时前
perftest 之 ib_write_bw --use-null-mr 参数详解:用 NULL MR 做 RDMA 带宽测试
java
2601_963870211 小时前
基于SSM的特产代购系统
java·前端