对象的可见性 - volatile篇
作者:汤圆
个人博客:javalover.cc
前言
官人们好啊,我是汤圆,今天给大家带来的是《对象的可见性 - volatile篇》,希望有所帮助,谢谢
文章如果有误,希望大家可以指出,真心感谢
简介
当一个线程修改了某个共享变量时(非局部变量,所有线程都可以访问得到),其他线程总是能立马读到最新值,这时我们就说这个变量是具有可见性的
如果是单线程,那么可见性是毋庸置疑的,肯定改了就能看到(直肠子,有啥说啥,大家都能看到)
但是如果是多线程,那么可见性就需要通过一些手段来维持了,比如加锁或者volatile修饰符(花花肠子,各种套路让人措手不及)
PS:实际上,没有真正的直肠子,据科学研究表明,人的肠子长达8米左右(~身高的5倍)
目录
- 单线程和多线程中的可见性对比
- volatile修饰符
- 指令重排序
- volatile和加锁的区别
正文
1. 单线程和多线程中的可见性对比
这里我们举两个例子来看下,来了解什么是可见性问题
下面是一个单线程的例子,其中有一个共享变量
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
0
10
改了就能看到,如果多线程也有这么简单,那多好(来自菜鸟的内心独白)。
下面我们看一个多线程的例子,还是那个共享变量
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;
}
}
输出如下:
你没看错,就是输出为空,而且程序还在一直运行(没有试过,如果不关机,会不会有输出number的那一天)
这时就出现了可见性问题,即主线程改了共享变量number,而子线程却看不到
原因是什么呢?
我们用图来说话吧,会轻松点
步骤如下:
- 子线程读取number到自己的栈中,备份
- 主线程读取number,修改,写入,同步到内存
- 子线程此时没有意识到number的改变,还是读自己栈中的备份ready(可能是各种性能优化的原因)
那要怎么解决呢?
加锁或者volatile修饰符,这里我们加volatile
修改后的代码如下:
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;
}
}
输出如下:
168
可以看到,跟我们预期的一样,子线程可以看到主线程做的修改
下面就让我们一起来探索volatile的小世界吧
2. volatile修饰符
volatile是一种比加锁稍弱的同步机制,它和加锁最大的区别就是,它不能保证原子性,但是它轻量啊
我们先把上面那个例子说完;
我们加了volatile修饰符后,子线程就可以看到主线程做的修改,那么volatile到底做了什么呢?
其实我们可以把volatile看做一个标志,如果虚拟机看到这个标志,就会认为被它修饰的变量是易变的,不稳定的,随时可能被某个线程修改;
此时虚拟机就不会对与这个变量相关的指令进行重排序(下面会讲到),而且还会将这个变量的改变实时通知到各个线程(可见性)
用图说话的话,就是下面这个样子:
可以看到,线程中的number备份都不需要了,每次需要number的时候,都直接去堆内存中读取,这样就保证了数据的可见性
3. 指令重排序
指令重排序指的是,虚拟机有时候为了优化性能,会把某些指令的执行顺序进行调整,前提是指令的依赖关系不能被破坏(比如int a = 10; int b = a;此时就不会重排序)
下面我们看下可能会重排序的代码:
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只是针对某个变量进行修饰,所以就有点像原子变量的复合操作(虽然原子变量本身是原子操作,但是多个原子变量放到一起,就无法保证了)
总结
- 可见性在单线程中没问题,但是多线程会有问题
- volatile是一种比加锁轻量级的同步机制,可以保证变量的可见性和有序性(禁止重排序)
- 指令重排序:有时虚拟机为了优化性能,会在运行时把相互没有依赖的代码顺序重新排序,以此来减少指令的等待时间,提高效率
- 加锁和volatile的区别:加锁可以保证原子性,volatile不可以
参考内容:
- 《Java并发编程实战》
- 《实战Java高并发》
后记
最后,感谢大家的观看,谢谢
原创不易,期待官人们的三连哟
对象的可见性 - volatile篇的更多相关文章
- jvm(三)指令重排 & 内存屏障 & 可见性 & volatile & happen before
参考文档: https://tech.meituan.com/java-memory-reordering.html http://0xffffff.org/2017/02/21/40-atomic- ...
- JAVA并发编程学习笔记------对象的可见性及发布逸出
一.非原子的64位操作: 当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值,这种安全性保证被称为最低安全性.最低安全性适用于绝大多数变量 ...
- 多线程(三)~多线程中数据的可见性-volatile关键字
我们先来看一段代码: ①.线程类,用全局布尔值控制线程是否结束,每隔1s打印一次当前线程的信息 package com.multiThread.thread; publicclassPrintStri ...
- Java内存可见性volatile
概述 JMM规范指出,每一个线程都有自己的工作内存(working memory),当变量的值发生变化时,先更新自己的工作内存,然后再拷贝到主存(main memory),这样其他线程就能读取到更新后 ...
- 深入理解javascript对象系列第三篇——神秘的属性描述符
× 目录 [1]类型 [2]方法 [3]详述[4]状态 前面的话 对于操作系统中的文件,我们可以驾轻就熟将其设置为只读.隐藏.系统文件或普通文件.于对象来说,属性描述符提供类似的功能,用来描述对象的值 ...
- Spring.NET学习笔记7——依赖对象的注入(基础篇) Level 200
1.person类 public class Person { public string Name { get; set; } public int Age { g ...
- 单例模式-全局可用的 context 对象,这一篇就够了
单例模式在各个方面都有着极为广泛的使用,所谓单例,顾名思义就是整个程序中只有一个该类的实例,所以它成功保证了整个程序的生命周期内该类的对象只能创建一次,并且提供全局唯一访问该类的方法:getInsta ...
- 可见性-volatile
出处: http://blog.csdn.net/vking_wang/article/details/9982709
- Java并发:volatile内存可见性和指令重排
volatile两大作用 1.保证内存可见性 2.防止指令重排 此外需注意volatile并不保证操作的原子性. (一)内存可见性 1 概念 JVM内存模型:主内存和线程独立的工作内存 Java内存模 ...
随机推荐
- Codeforces Global Round 13
比赛地址 A(水题) 题目链接 题目: 给出一个\(01\)序列,有2种操作:1.将某个位置取反:2.询问\(01\)序列中第\(k\)大的数 解析: 显然维护1的数目即可 #include<b ...
- PAT-1148(Werewolf )思维+数学问题
Werewolf PAT-1148 题目的要点是不管n规模多大,始终只有两个狼人 说谎的是一个狼人和一个好人 紧紧抓住这两点进行实现和分析 #include <iostream> #inc ...
- Nginx重定向到其他端口
location / { # limit_req zone=test_req burst=5 nodelay; return 302 http://$host:3000/; } # 我这里的端口为30 ...
- spring基础:什么是框架,框架优势,spring优势,耦合内聚,什么是Ioc,IOC配置,set注入,第三方资源配置,综合案例spring整合mybatis实现
知识点梳理 课堂讲义 1)Spring简介 1.1)什么是框架 源自于建筑学,隶属土木工程,后发展到软件工程领域 软件工程中框架的特点: 经过验证 具有一定功能 半成品 1.2)框架的优势 提高开发效 ...
- Python开发环境从零搭建-02-代码编辑器Sublime
想要从零开始搭建一个Python的开发环境说容易也容易 说难也能难倒一片开发人员,在接下来的一系列视频中,会详细的讲解如何一步步搭建python的开发环境 本文章是搭建环境的第2篇 讲解的内容是:安装 ...
- css实现0.5像素的底边框。
由于设计图的1px在移动端开发中的像素比是2倍,在实际开发中却是需要1px的线条,虽然最直接的方式是将线条设置为0.5px,但有些移动端对于0.5px的解析为0,变成了无边框的显示.因此处理该需求我们 ...
- css3中的渐变效果
大家好,这里是demo软件园,今天为大家分享的是css3中的渐变效果. css3中的渐变需要注意的是渐变的是图片而不是颜色,而渐变又分为两种:线性渐变与径向渐变,今天我们重点介绍的是线性渐变. 1.线 ...
- 【odoo14】第三章、创建插件
现在我们已经有了开发环境并了解了如何管理实例及数据库,现在让我们来学习下如何创建插件模块. 本章内容如下: 创建和安装模块 完成manifest文件 组织模块文件结构 添加模型 添加菜单及视图 添加访 ...
- 记一次jstack命令定位问题
今天天气不错,但是赶上恶意加班心情就不爽,怀着不爽的心情干活,总能创造出更多的问题,这不,今天就自己挖了一个坑,自己跳进去了,好在上来了 经过是这样的,开始调试canal采集binlog时,由于添加了 ...
- Banner信息扫描
Banner信息扫描 Banner一般用于表示对用户的欢迎,但其中可能包含敏感信息.获取Banner也属于信息搜索的范畴.在渗透测试中,典型的4xx.5xx信息泄露就属于Banner泄露的一种.在Ba ...