前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >对象的可见性 - volatile篇

对象的可见性 - volatile篇

原创
作者头像
汤圆学Java
修改于 2021-04-30 02:17:47
修改于 2021-04-30 02:17:47
86600
代码可运行
举报
文章被收录于专栏:汤圆学Java汤圆学Java
运行总次数:0
代码可运行

作者:汤圆

个人博客:javalover.cc

前言

官人们好啊,我是汤圆,今天给大家带来的是《对象的可见性 - volatile篇》,希望有所帮助,谢谢

文章如果有误,希望大家可以指出,真心感谢

简介

当一个线程修改了某个共享变量时(非局部变量,所有线程都可以访问得到),其他线程总是能立马读到最新值,这时我们就说这个变量是具有可见性的

如果是单线程,那么可见性是毋庸置疑的,肯定改了就能看到(直肠子,有啥说啥,大家都能看到)

但是如果是多线程,那么可见性就需要通过一些手段来维持了,比如加锁或者volatile修饰符(花花肠子,各种套路让人措手不及)

PS:实际上,没有真正的直肠子,据科学研究表明,人的肠子长达8米左右(~身高的5倍)

目录

  1. 单线程和多线程中的可见性对比
  2. volatile修饰符
  3. 指令重排序
  4. volatile和加锁的区别

正文

1. 单线程和多线程中的可见性对比

这里我们举两个例子来看下,来了解什么是可见性问题

下面是一个单线程的例子,其中有一个共享变量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class SignleThreadVisibilityDemo {
    // 共享变量
    private int number;
    public void setNumber(int number){
        this.number = number;
    }
    public int getNumber(){
        return this.number;
    }
    public static void main(String[] args) {
        SignleThreadVisibilityDemo demo = new SignleThreadVisibilityDemo();
        System.out.println(demo.getNumber());
        demo.setNumber(10);
        System.out.println(demo.getNumber());
    }
}

输出如下:可以看到,第一次共享变量number为初始值0,但是调用setNumber(10)之后,再读取就变成了10

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
0
10

改了就能看到,如果多线程也有这么简单,那多好(来自菜鸟的内心独白)。

下面我们看一个多线程的例子,还是那个共享变量

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package com.jalon.concurrent.chapter3;/**
 * <p>
 *  可见性:多线程的可见性问题
 * </p>
 *
 * @author: JavaLover
 * @time: 2021/4/27
 */
public class MultiThreadVisibilityDemo {
    // 共享变量
    private int number;
    public static void main(String[] args) throws InterruptedException {
        MultiThreadVisibilityDemo demo = new MultiThreadVisibilityDemo();
        new Thread(()->{
            // 这里我们做个假死循环,只有没给number赋值(初始化除外),就一直循环
            while (0==demo.number);
            System.out.println(demo.number);
        }).start();
        Thread.sleep(1000);
        // 168不是身高,只是个比较吉利的数字
        demo.setNumber(168);
    }public int getNumber() {
        return number;
    }public void setNumber(int number) {
        this.number = number;
    }}

输出如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

你没看错,就是输出为空,而且程序还在一直运行(没有试过,如果不关机,会不会有输出number的那一天)

这时就出现了可见性问题,即主线程改了共享变量number,而子线程却看不到

原因是什么呢?

我们用图来说话吧,会轻松点

步骤如下:

  1. 子线程读取number到自己的栈中,备份
  2. 主线程读取number,修改,写入,同步到内存
  3. 子线程此时没有意识到number的改变,还是读自己栈中的备份ready(可能是各种性能优化的原因)

那要怎么解决呢?

加锁或者volatile修饰符,这里我们加volatile

修改后的代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class MultiThreadVisibilityDemo {
    // 共享变量,加了volatile修饰符,此时number不会备份到其他线程,只会存在共享的堆内存中
    private volatile int number;
    public static void main(String[] args) throws InterruptedException {
        MultiThreadVisibilityDemo demo = new MultiThreadVisibilityDemo();
        new Thread(()->{
            while (0==demo.number);
            System.out.println(demo.number);
        }).start();
        Thread.sleep(1000);
        // 168不是身高,只是个比较吉利的数字
        demo.setNumber(168);
    }public int getNumber() {
        return number;
    }public void setNumber(int number) {
        this.number = number;
    }}

输出如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
168

可以看到,跟我们预期的一样,子线程可以看到主线程做的修改

下面就让我们一起来探索volatile的小世界吧

2. volatile修饰符

volatile是一种比加锁稍弱的同步机制,它和加锁最大的区别就是,它不能保证原子性,但是它轻量啊

我们先把上面那个例子说完;

我们加了volatile修饰符后,子线程就可以看到主线程做的修改,那么volatile到底做了什么呢?

其实我们可以把volatile看做一个标志,如果虚拟机看到这个标志,就会认为被它修饰的变量是易变的,不稳定的,随时可能被某个线程修改;

此时虚拟机就不会对与这个变量相关的指令进行重排序(下面会讲到),而且还会将这个变量的改变实时通知到各个线程(可见性)

用图说话的话,就是下面这个样子:

可以看到,线程中的number备份都不需要了,每次需要number的时候,都直接去堆内存中读取,这样就保证了数据的可见性

3. 指令重排序

指令重排序指的是,虚拟机有时候为了优化性能,会把某些指令的执行顺序进行调整,前提是指令的依赖关系不能被破坏(比如int a = 10; int b = a;此时就不会重排序)

下面我们看下可能会重排序的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ReorderDemo {
    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int m = a + b;
        int c = 1;
        int d = 2;
        int n = c - d;
    }
}

这里我们要了解一个底层知识,就是每一条语句的执行,在底层系统都是分好几步走的(比如第一步,第二步,第三步等等,这里我们就不涉及那些汇编知识了,大家感兴趣可以参考看下《实战Java高并发》1.5.4);

现在让我们回到上面这个例子,依赖关系如下:

可以看到,他们三三成堆,互不依赖,此时如果发生了重排序,那么就有可能排成下面这个样子

(上图只是从代码层面进行的效果演示,实际上指令的重排序比这个细节很多,这里主要了解重排序的思想先)

由于m=a+b需要依赖a和b的值,所以当指令执行到m=a+b的add环节时,如果b还没准备好,那么m=a+b就需要等待b,后面的指令也会等待;

但是如果重排序,把m=a+b放到后面,那么就可以利用add等待的这个空档期,去准备c和d;

这样就减少了等待时间,提升了性能(感觉有点像上学时候学的C,习惯性地先定义变量一大堆,然后再编写代码)

4. volatile和加锁的区别

区别如下

加锁

volatile

原子性

可见性

有序性

上面所说的有序性指的就是禁止指令的重排序,从而使得多线程中不会出现乱序的问题;

我们可以看到,加锁和volatile最大的区别就是原子性;

主要是因为volatile只是针对某个变量进行修饰,所以就有点像原子变量的复合操作(虽然原子变量本身是原子操作,但是多个原子变量放到一起,就无法保证了)

总结

  1. 可见性在单线程中没问题,但是多线程会有问题
  2. volatile是一种比加锁轻量级的同步机制,可以保证变量的可见性和有序性(禁止重排序)
  3. 指令重排序:有时虚拟机为了优化性能,会在运行时把相互没有依赖的代码顺序重新排序,以此来减少指令的等待时间,提高效率
  4. 加锁和volatile的区别:加锁可以保证原子性,volatile不可以

参考内容:

  • 《Java并发编程实战》
  • 《实战Java高并发》

后记

最后,感谢大家的观看,谢谢

原创不易,期待官人们的三连哟

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
java面试题:谈谈你对volatile的理解
  最近打算整理下Java面试中频率比较高,相对比较难的一些面试题,感兴趣的小伙伴可以关注下。
用户4919348
2020/04/16
1.2K0
Java多线程编程中之volatile详解
在Java多线程编程中,volatile关键字是一种重要的同步机制,可以理解为低配版synchronized,轻量级的同步策略,保证可见性,不保证原子性,禁止指令重排。它用于确保多线程环境下变量的可见性和顺序性。通过使用volatile关键字,可以避免线程之间的竞争条件和数据不一致性问题。本文将详细解释Java中的volatile关键字以及它在多线程编程中的应用。
小明爱吃火锅
2023/11/02
1.3K0
Java多线程编程中之volatile详解
简单了解下Java并发编程对象共享的可见性问题
可见性是一个复杂的属性,因为它经常违背我们的直觉。在单线程环境中,如果先写入某个变量的值,然后在没有其他写入操作的情况下读取该变量,程序总能得到相同的值,这是符合我们的期望的。然而,在多线程环境中,当读操作和写操作在不同的线程中执行时,情况却并非如此。通常情况下无法确保执行读操作的线程能够及时地看到其他线程写入的值,有时甚至是根本不可能的。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制来进行严格的同步和协调。
codetrend
2024/07/26
1010
Java多线程的可见性与有序性
如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量。
全菜工程师小辉
2019/08/16
5900
Java中volatile关键字的最全总结
volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。
哲洛不闹
2020/04/21
7K0
Java中volatile关键字的最全总结
谈谈Java中的volatile
volatile是Java提供的一种轻量级的同步机制,在并发编程中,它也扮演着比较重要的角色。同synchronized相比(synchronized通常称为重量级锁),volatile更轻量级,相比使用synchronized所带来的庞大开销,倘若能恰当的合理的使用volatile,自然是美事一桩。
哲洛不闹
2019/03/06
3240
谈谈Java中的volatile
走进volatile的世界,探索它与可见性,有序性,原子性之间的爱恨情仇!
在之前的几篇博文中,我们都提到了 volatile 关键字,这个单词中文释义为:不稳定的,易挥发的,在Java中代表变量修饰符,用来修饰会被不同线程访问和修改的变量,对于方法,代码块,方法参数,局部变量以及实例常量,类常量多不能进行修饰。
JavaBuild
2024/05/27
1860
走进volatile的世界,探索它与可见性,有序性,原子性之间的爱恨情仇!
java内存模型的理解
可见性定义: 一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为可见性。
大忽悠爱学习
2023/03/06
3170
java内存模型的理解
volitale 怎么保证可见性
(1)把工作内存1中更新过的共享变量刷新到主内存中 (2)将主内存中最新的共享变量的值更新到工作内存2中
袁新栋-jeff.yuan
2020/08/26
3.6K0
volitale 怎么保证可见性
浅谈Java多线程之内存可见性
这里是同一个外部类对象,然后外部类对象里面有2个内部类对象,相当于main里面的操作导致异步调用了read和write方法,这2个方法是都可以直接获取成员变量的。
砖业洋__
2023/05/06
1670
浅谈Java多线程之内存可见性
Java并发编程之volatile关键字
volatile关键字主要是用来解决共享变量内存可见性问题和CPU指令乱序执行问题。
布禾
2021/03/15
2500
反制面试官 | 14张原理图 | 再也不怕被问 volatile!
这一篇也算是Java并发编程的开篇,看了很多资料,但是轮到自己去整理去总结的时候,发现还是要多看几遍资料才能完全理解。还有一个很重要的点就是,画图是加深印象和检验自己是否理解的一个非常好的方法。
悟空聊架构
2020/08/19
3510
快速掌握并发编程---深入了解volatile
今天聊得这个volatile是一个轻量级的synchronized,它在多线程开发中保证了共享变量的“可见性”。
田维常
2020/11/03
5950
快速掌握并发编程---深入了解volatile
volatile与JMM
写完后 立即刷新回主内存并及时发出通知,大家可以去主内存拿最新版,前面的修改对后面所有线程可见
鱼找水需要时间
2023/02/16
2260
volatile与JMM
volatile vs synchronized
今天来聊一聊Java并发编程中两个常用的关键字:volatile和synchronized。在介绍这两个关键字之前,首先要搞明白并发编程中的两个问题:
Jackeyzhe
2020/03/11
3280
volatile vs synchronized
Java并发篇_volatile
volatile是Java提供的一种轻量级的同步机制。Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量,相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其使用也更容易出错。
栗筝i
2022/12/01
1990
Java并发篇_volatile
死磕juc(五)volatile与Java内存模型
内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性。
yuanshuai
2022/09/26
2790
死磕juc(五)volatile与Java内存模型
Java并发编程之 Java内存模型
JMM 即 Java Memory Model ,它从Java层面定义了 主存、工作内存 抽象概念,底层对应着CPU 寄存器、缓存、硬件内存、CPU 指令优化等。JMM 体现在以下几个方面:
冬天vs不冷
2025/01/21
670
Java并发编程之 Java内存模型
内存可见性和原子性:Synchronized和Volatile的比较
【尊重原创,转载请注明出处】http://blog.csdn.net/guyuealian/article/details/52525724
traffic
2020/04/09
1.5K0
内存可见性和原子性:Synchronized和Volatile的比较
volatile 详解
JMM本身是一种抽象的概念模型并不真实存在,塔描述的是一组规则或规范,通过这组规范定义了程序中各个变量(实例字段、静态字段、构成数组对象的元素)的访问方式
ruochen
2021/12/16
1.6K0
相关推荐
java面试题:谈谈你对volatile的理解
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文