一文精通Java中的volatile关键字

作者:架构与我 时间:2023-11-24 04:17:14 

前言

在一些开源的框架的源码当中时不时都可以看到volatile这个关键字,最近特意学习一下volatile关键字的使用方法。

volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。 相较于 synchronized 是一种较为轻量级的同步策略。

缺点:

1. volatile 不具备“互斥性”

2. volatile 不能保证变量的“原子性”

很多资料中是这样介绍volatile关键字的:

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

文字不太好理解,通过例子来理解。

1、例子

首先看一个没有使用volatile关键字例子:


package com.swnote.java;

/**
* volatile测试例子
*
* @author lzj
* @date [2019-04-47]
*/
public class VolatileTest {

private boolean flag;

public static void main(String[] args) {
VolatileTest test = new VolatileTest();
test.test();
}

public void test() {
new Thread(() -> {
 try {
 Thread.sleep(1000L);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 flag = true;
}).start();

new Thread(() -> {
 while (true) {
 if (flag) {
  System.out.println("thread flag = " + flag);
 }
 }
}).start();
}
}

该例子中定义了一个flag共享变量,test方法里面开启了两个线程,第一个线程在等待1秒中后修改共享变量flag的值为true,第二个线程通过循环判断flag的值,当flag的值为true时,输出内容。

此时有两种猜想:

  • 执行后,可以看到输出内容,即说明第二个线程能够感知到第一个线程对共享变量flag的修改

  • 执行后,没有任务内容,即说明第二个线程无法感知到第一个线程对共享变量flag的修改

然后执行结果为:

一文精通Java中的volatile关键字

没有任务的输出内容,即证明了此时这样情况下第二个线程无法感知到第一个线程对共享变量flag的修改的

现在修改一下例子,即为flag变量加上volatile关键字,即:


private volatile boolean flag;

然后再运行,此时结果为:

一文精通Java中的volatile关键字

此时就有内容输出了,说明加上volatile关键字后,第二个线程可以感知到第一个线程对共享变量flag的修改的,这就是上面概念中所说的volatile在多处理器开发中保证了共享变量的“可见性”。

2、原理

通过上面的例子证明了volatile在多处理器开发中保证了共享变量的“可见性”,那它是怎么实现的呢?

这时就得介绍一下Java的内存模型了,首先看如下示意图:

一文精通Java中的volatile关键字

Java内存模型是如上面所示的:

共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在自己的本地内存中进行,而不能直接读写主内存中的变量。

根据此理解,上述例子的在没有加volatile时的情况是这样的:

第一个线程从主内存中获取共享变量flag的值,此时值为false,将该值放到自己的本地内存中,然后对变量进行修改,将值改为true,此时也只是将本地内存中flag的值改为了true,此时还没有将值同步到主内存中,然后第二线程也是将共享变量flag的值放到自己的本地内存中,而此时flag的值还是为false,所以就是一直没有内容输出了。

然而加上volatile关键字后,第一个线程对flag的修改会强制刷新到主内存中去,同时还会导致其他线程中的本地内存的值会无效,需要重新到主内存获取,这样就保证了第一个线程对flag修改后,第二线程能够感知到。

3、注意点

volatile是轻量级的synchronized,但是它是不能够代替synchronized的,因为volatile只能保证原子性操作的安全,对于复合操作,volatile是不能保证线程安全的。

例如:


package com.swnote.java;

/**
* 复合操作例子
*
* @author lzj
* @date [2019-04-27]
*/
public class StatisticTest {
private volatile int num = 0;

public static void main(String[] args) {
 StatisticTest test = new StatisticTest();
 test.statistic();
}

public void statistic() {
 for (int i = 0; i < 20; i++) {
  new Thread(() -> {
   num++;
  }).start();
 }

System.out.println("num = " + num);
}
}

期望的运行结果是20,可是几乎每次运行结果都是不一样的,例如有的结果为:

一文精通Java中的volatile关键字

这是因为num++这个操作不是原子性的,所以即使使用了volatile关键字,也是不能保证安全的。

来源:https://www.cnblogs.com/atcloud/p/10819111.html

标签:java,volatile,关键字
0
投稿

猜你喜欢

  • 关于JDK8中的字符串拼接示例详解

    2021-10-27 13:38:59
  • 重写hashCode()和equals()方法详细介绍

    2023-11-24 16:13:33
  • Java实现bmp和jpeg图片格式互转

    2023-12-19 10:39:50
  • spring声明式事务 @Transactional 不回滚的多种情况以及解决方案

    2023-07-06 10:50:33
  • Android开发入门环境快速搭建实战教程

    2022-06-17 15:59:41
  • c#获取两个特定字符之间的内容并输出的方法

    2021-12-02 19:47:11
  • java将一个目录下的所有数据复制到另一个目录下

    2023-01-08 15:11:44
  • Java 使用Socket正确读取数据姿势

    2023-09-16 12:13:43
  • Android基于ImageView绘制的开关按钮效果示例

    2023-02-22 19:39:11
  • java开发中使用IDEA活动模板快速增加注释的方法

    2021-09-25 20:42:24
  • 解决java执行cmd命令调用ffmpeg报错Concat error - No such filter '[0,0]'问题

    2023-03-14 20:35:11
  • 基于Java利用static实现单例模式

    2021-12-20 06:07:32
  • android 自定义圆角button效果的实例代码(自定义view Demo)

    2022-07-13 11:35:09
  • C#实现启用与禁用本地网络的方式小结【3种方式】

    2022-04-21 18:45:14
  • Java Web开发过程中登陆模块的验证码的实现方式总结

    2022-01-29 19:33:16
  • Android中Property模块的键值设置

    2021-10-14 08:40:25
  • Java中的内存泄露问题和解决办法

    2022-05-12 20:02:35
  • SpringBoot应用War包形式部署到外部Tomcat的方法

    2021-10-06 04:59:48
  • 关于Android实现简单的微信朋友圈分享功能

    2021-07-01 16:49:49
  • Java实现计算器设计

    2023-08-18 13:36:54
  • asp之家 软件编程 m.aspxhome.com