使用volatile对其他线程实时可见

本文通过一个实例展示了volatile关键字如何确保线程间的变量实时可见性。在没有volatile的情况下,线程可能无法立即获取其他线程对共享变量的最新修改,导致死循环。volatile修饰变量后,线程能及时获取主内存的最新值,从而避免此类问题。但要注意,volatile并不保证操作的原子性,对于某些场景,可能需要结合其他同步机制如CountDownLatch来确保正确性。

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

使用volatile对其他线程实时可见

背景:

今天继续做白老师布置的作业,今天来设计一个小场景来演示用volatile修饰的变量对其他线程的可见性。

设计场景:

  1. 设计两个线程,第一个线程往已经定义好的list里面不断添加元素。
  2. 第二个线程不断读取这个list,当发现size等于10的时候,就输出日志并终止循环。
    我们看这个list在有volatile修饰和没volatile修饰的区别。

代码:

先随便定义一个Bean:

package top.usjava.learn.javaarchitecturelearn.vo;

public class Cat {
    String name;
    int age;

    public Cat(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

接下来是测试类(list变量没有添加volatile修饰):

package top.usjava.learn.javaarchitecturelearn.unitlearn;

import top.usjava.learn.javaarchitecturelearn.vo.Cat;

import java.util.ArrayList;
import java.util.List;

public class TestVolatile {
    List<Cat> list = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        TestVolatile testVolatile = new TestVolatile();

        Thread t1 = new Thread(()->{
            for(int i =0;i<100;i++){
                testVolatile.list.add(new Cat("a"+i,i));
            }
            System.out.println("已经添加完100个cat了");
        });

        Thread t2 = new Thread(()->{
            while (true){
                if (testVolatile.list.size()==10){
                    System.out.println("检测到list的size已经到10了");
                    break;
                }
            }
        });

        t2.start();
        Thread.sleep(1000L);
        t1.start();
    }
}

结果如图:
没有volatile修饰的结果

我们从图中看到,list都已经添加完100个元素了,第二个线程都还没检测出来,还在不断循环检测,现在就变成了死循环了。因为list现在size已经是100了。(注意:线程1在跑之前,线程2已经在循环监听了。因为main方法里先让t2线程跑了,休眠1秒再让t1跑)

接下来把list加上volatile修饰看看:

volatile List<Cat> list = new ArrayList<>();

运行结果:
有volatile修饰的结果

可以看到,加了volatile修饰后,线程2是可以检测到出来的。

原因:

这里简单地进行说明一下原因,深层次的分析可以查看其他博客或者相关书籍。

先看下图:
volatile内存模型

首先,说一下内存的一点点概念,就是程序运行时,有两个内存空间,一个是主内存,一个是工作内存。主内存里的变量是对各个线程都是可见的,而工作内存是每个线程自己的独占的内存空间,其他线程是看不到的。

知道这个概念后,再来描述下一般程序在没有用volatile时是如何对变量进行修改的:
1. 首先,线程1会从主内存读取和加载变量。
2. 然后,线程1会把该变量copy一份放到自己的工作内存里。
3. 接着,线程1对自己工作内存的该变量进行修改,修改完后,再写入主内存,更新主内存的值,这样,其他线程就可以通过主内存拿到最新的值了。但是注意:不是修改完工作内存的值后马上就写入主内存,这个主要看jvm和操作系统的调度了。

因此,没有用volatile时,如果其他线程在线程1写入主内存前读取该变量,那得到的值肯定是旧值。所以,上面代码的第一次运行结果就是这个原因。就是线程1在修改list时,不一定是在size等于10的时候写入主内存,所以线程2一直检测不到10的出现。

如果用volatile修饰,对比不用的情况,区别就是在修改变量后,线程马上把新值更新到主内存,然后其他线程再读取这个变量时,就拿到了最新的值了。所以上面代码第二次运行的结果是可以检测到的。

注意:

上面我代码的例子其实就算用volatile关键字修饰,也不是每次都可以检测成功的。因为虽然说是实时更新到主内存,但是由于线程2不一定是在size等于10的时候取值,所以有时也会检测不出来,这个时候就要可以用CountDownLatch这种方案来解决了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值