深入理解volatile关键字一


前言

volatile是java的一个关键字,我们在很多地方都有看到。接下来我们一起探讨下这个关键字的作用和使用场景


一、volatile概述

volatile作为java的关键字,用来修饰变量,在多线程的场景下,能够保证程序的有序性和可见性。不用sychonized关键字就能实现线程安全。

二、问题引入

1.例子

代码如下(示例):

public class VolatileTest {

    private static volatile int INIT_VALUE = 0;

    private final static int MAX_LIMIT = 500;

    public static void main(String[] args) {
        new Thread(() -> {
            int localValue = INIT_VALUE;
            while (localValue < MAX_LIMIT) {
                if (localValue != INIT_VALUE) {
                    System.out.printf("The value updated to [%d]\n", INIT_VALUE);
                    localValue = INIT_VALUE;
                }
            }
        }, "READER").start();

        new Thread(() -> {
            int localValue = INIT_VALUE;
            while (INIT_VALUE < MAX_LIMIT) {
                System.out.printf("Update the value to [%d]\n", ++localValue);
                INIT_VALUE = localValue;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "UPDATER").start();
    }
}

2.执行结果分析

这段程序执行的结果为
update the value to [1]
The value updated to [1]
update the value to [2]
The value updated to [2]
update the value to [3]
The value updated to [3]
update the value to [4]
The value updated to [4]
update the value to [5]
The value updated to [5]

我们把INIT_VALUE 的volatile关键字去掉,执行结果为
update the value to [1]
update the value to [2]
update the value to [3]
update the value to [4]
update the value to [5]
READER线程陷入死循环。为什么会出现这种问题呢?接下来我们先看看java内存模型

3.java内存模型

在讲java内存模型之前,我们先看看cpu是如何执行我们的java程序的,大致有以下两个步骤:
首先、java编译器会将我们的代码编译成汇编指令
然后、汇编指令会在jvm处理之后转成cpu指令,发送给cpu执行

我们都知道汇编指令是操作数据的过程,而cpu执行程序的过程中从哪里拿数据呢?说到这,我们想到了我们工作常见的一种情况:application、缓存、数据库。为了减轻数据库的压力,我们会加缓存,比如redis等,application在执行的过程中访问数据,可能会先到缓存中去拿,当我们给数据库插入数据后,缓存也会做相应的更新,保证数据的一致性。

而cpu拿数据也是差不多的,cpu和内存RAM之间会有一个类似缓存的角色chacheline。如下图
在这里插入图片描述

而java的内存模型也是差不多的:有一个主内存,每个线程都有一份主内存的缓存。当我们执行程序的时候为了提高程序的执行效率,线程会从自己的缓存中去取数据,而不会主动到主内存中去拿。这就导致了一个问题,线程之间的的缓存数据不一致。jmm-java内存模型如下图:
在这里插入图片描述
看完java内存模型之后,线程READER线程陷入死循环的问题就很好解释了
WRIDTER线程和READER线程都有自己的一份缓存,当线程运行的过程中会到自己的缓存中去拿数据。所以当没有volatile关键字修饰INIT_VALUE的时候,WRITER线程虽然改变了INIT_VALUE的值,但是由于READER线程只到自己缓存中的数据不会到主内存去拿数据,导致READER中的INIT_VALUE的数据一直为0。所以程序就陷入了死循环。而当INIT_VALUE有volatile关键字修饰之后。READER线程会到主内存中去拿数据。这样就保证了数据可见性,也就是WRITER线程在更改数据之后READER线程对INIT_VALUE的数据可见。保证了数据的一致性和线程安全。让程序能够正常执行。

扩展问题,当有volatile关键字修饰的时候,线程会到住内存中去拿数据。那当没有volatile关键字修饰的时候,就一直不会到主内存中去拿数据吗?答案分两种情况。
第一种:如下截图:
在这里插入图片描述
这里的话是永远都不会到主内存中拿数据的

第二种:如果上面的代码对INITE_VALUE有写操作,则还是会到主内存拿数据的,但是并不能够保证线程安全。

为什么出现这两种情况呢?是因为java的优化机制,没有写操作,java认为数据没有改变,故永远不会到主内存拿数据。如果有写操作,线程在执行的过程中还是会到主内存中去拿数据的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值