作者:小傅哥

博客:https://bugstack.cn

沉淀、分享、成长,让自己和他人都能有所收获!

一、码场心得

你是个能吃苦的人吗?

从前的能吃苦大多指的体力劳动的苦,但现在的能吃苦已经包括太多维度,包括:读书学习&寂寞的苦深度思考&脑力的苦自律习惯&修行的苦自控能力&放弃的苦低头做人&尊严的苦

虽然这些苦摆在眼前,但大多数人还是喜欢吃简单的苦。熬夜加班、日复一日、重复昨天、CRUD,最后身体发胖、体质下降、能力不足、自抱自泣!所以有些苦能不吃就不吃,要吃就吃那些有成长价值的苦。

今天你写博客了吗?

如果一件小事能坚持5年以上,那你一定是很了不起的人。是的,很了不起。人最难的就是想清楚了但做不到,或者偶尔做到长期做不到。

其实大多数走在研发路上的伙伴们,都知道自己该努力,但明明下好了的决心就是坚持不了多久。就像你是否也想过要写技术博客,做技术积累。直到有一天被瓶颈限制在困局中才会着急,但这时候在想破局就真的很难了!

二、面试题

谢飞机,小记,飞机趁着周末,吃完火锅。又去约面试官喝茶了!

谢飞机:嗨,我在这,这边,这边。

面试官:你怎么又来了,最近学的不错了?

谢飞机:还是想来大厂,别害羞,面我吧!

面试官:我好像是你补课老师... 既然来了,就问问你吧!volatile 是干啥的?

谢飞机:啊,volatile 是保证变量对所有线程的可见性的。

面试官:那 volatile 可以解决原子性问题吗?

谢飞机:不可以!

面试官:那 volatile 的底层原理是如何实现的呢?

谢飞机:...,这!面试官,刚问两个题就甩雷,你是不家里有事要忙?

面试官:你管我!

三、volatile 讲解

1. 可见性案例

public class ApiTest {

    public static void main(String[] args) {
final VT vt = new VT(); Thread Thread01 = new Thread(vt);
Thread Thread02 = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException ignore) {
}
vt.sign = true;
System.out.println("vt.sign = true 通知 while (!sign) 结束!");
}
}); Thread01.start();
Thread02.start();
} } class VT implements Runnable { public boolean sign = false; public void run() {
while (!sign) {
}
System.out.println("你坏");
}
}

这段代码,是两个线程操作一个变量,程序期望当 sign 在线程 Thread01 被操作 vt.sign = true 时,Thread02 输出 你坏

但实际上这段代码永远不会输出 你坏,而是一直处于死循环。这是为什么呢?接下来我们就一步步讲解和验证。

2. 加上volatile关键字

我们把 sign 关键字加上 volatitle 描述,如下:

class VT implements Runnable {

    public volatile boolean sign = false;

    public void run() {
while (!sign) {
}
System.out.println("你坏");
}
}

测试结果

vt.sign = true 通知 while (!sign) 结束!
你坏 Process finished with exit code 0

volatile关键字是Java虚拟机提供的的最轻量级的同步机制,它作为一个修饰符出现,用来修饰变量,但是这里不包括局部变量哦

在添加 volatile 关键字后,程序就符合预期的输出了 你坏。从我们对 volatile 的学习认知可以知道。volatile关键字是 JVM 提供的最轻量级的同步机制,用来修饰变量,用来保证变量对所有线程可见性。

正在修饰后可以让字段在线程见可见,那么这个属性被修改值后,可以及时的在另外的线程中做出相应的反应。

3. volatile怎么保证的可见性

3.1 无volatile时,内存变化

首先是当 sign 没有 volatitle 修饰时 public boolean sign = false;,线程01对变量进行操作,线程02并不会拿到变化的值。所以程序也就不会输出结果 “你坏”

3.2 有volatile时,内存变化

当我们把变量使用 volatile 修饰时 public volatile boolean sign = false;,线程01对变量进行操作时,会把变量变化的值强制刷新的到主内存。当线程02获取值时,会把自己的内存里的 sign 值过期掉,之后从主内存中读取。所以添加关键字后程序如预期输出结果。

4. 反编译解毒可见性

类似这样有深度的技术知识,最佳的方式就是深入理解原理,看看它到底做了什么才保证的内存可见性操作。

4.1 查看JVM指令

指令javap -v -p VT

 public volatile boolean sign;
descriptor: Z
flags: ACC_PUBLIC, ACC_VOLATILE org.itstack.interview.test.VT();
descriptor: ()V
flags:
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2 // Field sign:Z
9: return
LineNumberTable:
line 35: 0
line 37: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lorg/itstack/interview/test/VT; public void run();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field sign:Z
4: ifne 10
7: goto 0
10: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
13: ldc #4 // String 你坏
15: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
18: return
LineNumberTable:
line 40: 0
line 42: 10
line 43: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 this Lorg/itstack/interview/test/VT;
StackMapTable: number_of_entries = 2
frame_type = 0 /* same */
frame_type = 9 /* same */
}

从JVM指令码中只会发现多了,ACC_VOLATILE,并没有什么其他的点。所以,也不能看出是怎么实现的可见性。

4.2 查看汇编指令

通过Class文件查看汇编,需要下载 hsdis-amd64.dll 文件,复制到 JAVA_HOME\jre\bin\server目录下。下载资源如下:

另外是执行命令,包括:

  1. 基础指令:java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly
  2. 指定打印:-XX:CompileCommand=dontinline,类名.方法名
  3. 指定打印:-XX:CompileCommand=compileonly,类名.方法名
  4. 输出位置:> xxx

最终使用:java -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=dontinline,ApiTest.main -XX:CompileCommand=compileonly,ApiTest.mian

指令可以在IDEA中的 Terminal 里使用,也可以到 DOS黑窗口中使用

另外,为了更简单的使用,我们把指令可以配置到idea的 VM options 里,如下图:

配置完成后,不出意外的运行结果如下:

Loaded disassembler from C:\Program Files\Java\jdk1.8.0_161\jre\bin\server\hsdis-amd64.dll
Decoding compiled method 0x0000000003744990:
Code:
Argument 0 is unknown.RIP: 0x3744ae0 Code size: 0x00000110
[Disassembling for mach='amd64']
[Entry Point]
[Constants]
# {method} {0x000000001c853d18} 'getSnapshotTransformerList' '()[Lsun/instrument/TransformerManager$TransformerInfo;' in 'sun/instrument/TransformerManager'
# [sp+0x40] (sp of caller)
0x0000000003744ae0: mov r10d,dword ptr [rdx+8h]
0x0000000003744ae4: shl r10,3h
0x0000000003744ae8: cmp r10,rax
0x0000000003744aeb: jne 3685f60h ; {runtime_call}
0x0000000003744af1: nop word ptr [rax+rax+0h]
0x0000000003744afc: nop
[Verified Entry Point]
0x0000000003744b00: mov dword ptr [rsp+0ffffffffffffa000h],eax
0x0000000003744b07: push rbp
0x0000000003744b08: sub rsp,30h ;*aload_0
; - sun.instrument.TransformerManager::getSnapshotTransformerList@0 (line 166) 0x0000000003744b0c: mov eax,dword ptr [rdx+10h]
0x0000000003744b0f: shl rax,3h ;*getfield mTransformerList
; - sun.instrument.TransformerManager::getSnapshotTransformerList@1 (line 166) 0x0000000003744b13: add rsp,30h
...

运行结果就是汇编指令,比较多这里就不都放了。我们只观察

面经手册 · 第14篇《volatile 怎么实现的内存可见?没有 volatile 一定不可见吗?》的更多相关文章

  1. JS魔法堂:不完全国际化&本地化手册 之 实战篇

    前言  最近加入到新项目组负责前端技术预研和选型,其中涉及到一个熟悉又陌生的需求--国际化&本地化.熟悉的是之前的项目也玩过,陌生的是之前的实现仅仅停留在"有"的阶段而已. ...

  2. Mysql高手系列 - 第14篇:详解事务

    这是Mysql系列第14篇. 环境:mysql5.7.25,cmd命令中进行演示. 开发过程中,会经常用到数据库事务,所以本章非常重要. 本篇内容 什么是事务,它有什么用? 事务的几个特性 事务常见操 ...

  3. 面经手册 · 第17篇《码农会锁,ReentrantLock之AQS原理分析和实践使用》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 如果你相信你做什么都能成,你会自信的多! 千万不要总自我否定,尤其是职场的打工人.如 ...

  4. 面经手册 · 第20篇《Thread 线程,状态转换、方法使用、原理分析》

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

  5. 基础篇:深入JMM内存模型解析volatile、synchronized的内存语义

    目录 1 java内存模型,JMM(JAVA Memory Model) 2 CPU高速缓存.MESI协议 3 指令重排序和内存屏障指令 4 happen-before原则 5 synchronize ...

  6. 面经手册 · 第18篇《AQS 共享锁,Semaphore、CountDownLatch,听说数据库连接池可以用到!》

    作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...

  7. [面试]volatile类型修饰符/内存屏障/处理器缓存

    volatile类型修饰符 本篇文章的目的是为了自己梳理面试知识点, 在这里做一下笔记. 绝大部分内容是基于这些文章的内容进行了copy+整理: 1. http://www.infoq.com/cn/ ...

  8. 黑马-----内存模型和volatile详解

    黑马程序员:Java培训.Android培训.iOS培训..Net培训 JAVA线程-内存模型和volatile详解 一.单核内存模型 1.程序运行时,将临时数据存放到Cache中 2.将CPU计算所 ...

  9. Java内存模型:volatile详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt202 Java内存模型:volatile是干什么用的Volatile字段是用 ...

随机推荐

  1. 看动画学算法之:排序-count排序

    目录 简介 count排序的例子 count排序的java实现 count排序的第二种方法 count排序的时间复杂度 简介 今天我们介绍一种不需要作比较就能排序的算法:count排序. count排 ...

  2. 虚拟机栈(Java Stack)基础知识

    虚拟机栈即是程序运行时的单位,而堆是数据存储的单位.换句话说,栈解决的是程序运行的问题,即程序如何执行,如何处理数据,而堆是解决数据的存储问题,数据存在哪,放在哪 虚拟机栈细节如下图所示,其中当前栈帧 ...

  3. SSM框架整合 IDEA_Maven

    首先是配置web的web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app versio ...

  4. 你可能不知道的 Date 类

    Date 是 JS 中的重要的一个内置对象,其实例主要用于处理时间和日期,其时间基于 1970-1-1 (世界标准时间)起的毫秒数,时间戳长度为 13 位(不同于 Unix 时间戳的长度 10 位). ...

  5. Kafka索引设计的亮点

    前言 其实这篇文章只是从Kafka索引入手,来讲述算法在工程上基于场景的灵活运用.单单是因为看源码的时候有感而写之. 索引的重要性 索引对于我们来说并不陌生,每一本书籍的目录就是索引在现实生活中的应用 ...

  6. RT-Thread小知识点收集

  7. java 线程池、多线程实战(生产者消费者模型,1 vs 10) 附案例源码

    导读 前二天写了一篇<Java 多线程并发编程>点我直达,放国庆,在家闲着没事,继续写剩下的东西,开干! 线程池 为什么要使用线程池 例如web服务器.数据库服务器.文件服务器或邮件服务器 ...

  8. NOI 2012 【迷失游乐园】

    这道题,额,反正我是刚了2天,然后就萎了......(是不是觉得我很菜) 题目描述: 放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩. 进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐 ...

  9. Git本地已有项目关联远程仓库

    情况: 本地已有项目 远程有个仓库 目的: 本地项目关联远程仓库 首先要把本地项目变成git管理的,也就是建立一个本地仓库,可以在项目目录下面使用git init命令初始化仓库,初始化成功之后会在仓库 ...

  10. .NET Standard 类库的使用技巧

    系列目录     [已更新最新开发文章,点击查看详细] 在前一篇博客<.NET Standard中配置TargetFrameworks输出多版本类库>中详细介绍了如何创建.配置.条件编译. ...