javase - 多线程之volatile

本文深入解析Java中Volatile关键字的两大特性:实时可见性和禁止指令重排序。通过具体示例阐述Volatile如何确保多线程环境下变量修改的即时可见,并探讨其在原子性方面的局限性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

目录

特性(一):实时可见性

特性(二):禁止指令重排序


 volatile :英文的字面意思是易变的、不稳定的。

特性(一):实时可见性

(什么意思?一个线程对属性进行写操作,通知其他线程。告诉他们,这个属性已经发生了改变。)

通过一个demo讲解下。如下:

package com.test.testDemo3;

import com.test.testThread.TestThread;

import java.util.concurrent.TimeUnit;

public class TestDemo1 extends Thread {

    private static volatile boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if (flag)
                    System.out.println("test success");
            }
        }).start();
        Thread thread = new TestDemo1();
        thread.start();
        TimeUnit.SECONDS.sleep(2);
        TestDemo1.flag = true;
    }
}

启动了两个线程。之后sleep两秒。再修改TestDemo1线程的flag值为true。

运行看看:

test success
test success
test success
test success
test success
test success
test success

会一直输出 test success。说明什么? 说明TestDemo1通过对flag 设置volatile。再进行写操作,做出了改变。然后会通知另一个线程,另一个线程会根据这个变化后的flag做出处理,执行if语句中的代码。

注:作为比较,去掉 volatile 试试,看看结果。

再看一个例子:

public class TestDemo1 extends Thread {

    private static volatile boolean flag = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            while (true) {
                if (flag)
                    System.out.println("test success");
            }
        }).start();
        Thread.sleep(2000);
        Thread thread = new TestDemo1();
        TestDemo1.flag = true;
        thread.start();
    }
}

这个例子与上面的类似,区别是TestDemo1是在启动之前修改flag的值。

也会一直输出:

test success
test success
test success
test success

去掉volatile,就会什么都不输出。

为什么要让sleep两秒呢? 因为会发生指令重排序。线程的运行先后顺序并不能确定,这就导致可能TestDemo1先运行。那这个测试就没有意义了。

从上面的例子以及叙述能得出:

一个线程对volatile修饰的属性进行写操作,会通知其他线程,(包括正在运行中的线程)。告诉他们,这个属性已经发生了改变。体现一种实时性。

volatile底层基于内存屏障。

关于内存屏障:

编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障, 相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同主内存的工作内存。例如,一个写屏障会 把这个屏障前写入的数据刷新到工作内存,这样任何试图读取该数据的线程将得到最新值

(节选于:https://www.cnblogs.com/churao/p/8494160.html

特性(二):禁止指令重排序

先来谈一下 volatile 并不能保证原子性。

通过两个线程,对一个变量i,进行++操作。即i++

public class TestDemo1 extends Thread {

    private static volatile int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 10000; j++) {
            i++;
        }
    }

    public static void main(String[] args) {
        for (int j = 0; j < 5; j++) {
            new TestDemo1().start();
        }
        System.out.println(i);
    }
}

即使有volatile,结果也不一定是50000。先来看如下图所示:

可能会发生这样一种情况:线程A将i = 1,传到主内存的同时,B正在读取i的值。i = 0,再进行+ 1操作,i = 1。这时,主内存向各个工作内存通知i已有线程A 变更为  1 。但是线程B进行+ 1操作后已经变更为1了。再次将i = 1,传给主内存。造成结果不等于50000。 

最后再谈谈禁止指令重排序。

详见:

https://mp.weixin.qq.com/s/zn8e7nKUPXq4G4CzyE7rPA

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值