最后一面挂在volatile关键字上,面试官:重新学学Java吧!
最后一面挂在volatile关键字上,面试官:重新学学Java吧!
为什么会有volatile关键字?
volatile: 易变的; 无定性的; 无常性的; 可能急剧波动的; 不稳定的; 易恶化的; 易挥发的; 易发散的;
从上面的单词本意我们可以知道这个关键词用于修饰那些易变的变量
为了让我们更好理解为什么volatile这个关键字的作用以及存在的意义
我们先来看一段代码:
package com.laoqin.juc;
/**
* @Description TODO 测试volatile关键字
* @author LaoQin
*/
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true){
if(td.isFlag()){
System.out.println("主线程获取到flag为true");
break;
}
}
}
}
class ThreadDemo implements Runnable{
private boolean flag = false;
@Override
public void run() {
/*睡眠2秒*/
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag="+ isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
这段代码想表述的逻辑很简单,内部类实现了Runnable接口,run方法内部对flag进行了简单的赋值操作,并在主线程中写了一个死循环去不断判断flag的值,只要为true那么这个程序就会结束运行
但是事实也是如此吗?
我们等待了很久依然等不到程序结束,这是为什么呢?
我们得到的结果如下
flag=true
说明在线程内部我们的flag值确实是true,但是在主线程中的flag却一直是false,这就造成了我们的循环成为一个真正的"死循环"
这就涉及到一个"内存可见性"的问题了
内存可见性
JVM为了提升程序的运行效率,会为我们程序当中每一个线程分配一个独立的"缓存空间",这个"缓存空间"对应着jvm调优参数中的 -Xss512k ,这表示为每个线程分配的"缓存空间"为512kb
程序运行过程中首先会有一个主存,拿上面的例子来说
主存中 flag = false;
然后启动两个线程,一个是读(主线程),一个是写(子线程)
因为子线程中休眠了2秒,所以是主线程先执行
因为子线程要改变数据,所以子线程是先把flag=false这条数据读到自己的"缓存"中来
然后在自己的内存空间先改变这个副本,然后再把这个值写回到主存中去
数据的运算都是在缓存中执行
主线程在子线程还没有改变主存值的时候就已经读取了false到自己的缓存
因为主线程调用的是while(true),JVM会调用系统底层代码,执行效率很高
甚至高到主线程没有机会再去主存中获取数据
这就是一个典型的内存可见性问题:
即两个线程在共享同一数据的时,共享数据的所有操作对于每个独立内存来说都是不可见的
对于以上的问题,我们可以通过同步锁来解决
package com.laoqin.juc;
/**
* @Description TODO 测试volatile关键字
* @author LaoQin
*/
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true){
//改动了这里
synchronized (td){
if(td.isFlag()){
System.out.println("主线程获取到flag为true");
break;
}
}
}
}
}
class ThreadDemo implements Runnable{
private boolean flag = false;
@Override
public void run() {
/*睡眠2秒*/
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag="+ isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
这样通过给对象加锁,就可以解决这个问题,即保持多个线程之间数据的同步(锁住共享数据或者共享数据所在对象)
得到以下效果
主线程获取到flag为true
flag=true
但是加锁意味着我们程序的效率将会变得极其低下
当有多个线程同时访问的时候,后来的线程必须要等待前面的线程释放被锁资源才能进行操作
这就是volatile存在的意义
volatile用法
volatile能保证多个线程在操作同一个数据时,这个数据对于所有线程来说是可见的
底层是因为volatile会让jvm去调用计算机底层的"内存栅栏"
内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。
语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。因此,对于敏感的程序块,写操作之后、读操作之前可以插入内存屏障。
我们可以简单理解为volatile能让所有线程的操作都在主存中完成,这样就规避了内存可见性的问题
这样会比锁的效率高很多,但是还是会比不加该关键字运行效率低不少
原因是JVM底层优化逻辑中对violatile修饰的变量会进行重排序,这个会比较耗时
以下是使用volatile的代码
package com.laoqin.juc;
/**
* @Description TODO 测试volatile关键字
* @author LaoQin
*/
public class TestVolatile {
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
new Thread(td).start();
while (true){
if(td.isFlag()){
System.out.println("主线程获取到flag为true");
break;
}
}
}
}
class ThreadDemo implements Runnable{
//改动了这里
private volatile boolean flag = false;
@Override
public void run() {
/*睡眠2秒*/
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag="+ isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
得到的结果和加锁的一样
主线程获取到flag为true
flag=true
volatile和synchronized异同
相较于synchronized,volatile是一种轻量级的"数据同步策略"
但是volatile不具备"互斥性",即synchronized修饰的数据一旦上锁后别的线程是无法操作该数据的,但volatile修饰的变量只是会让该数据在主存中完成操作,并不会让数据具有"互斥性"
同时volatile也不能保证变量的"原子性",即volatile不能保证变量是一个不可分割的整体
相信通过这篇文章简短的叙述,各位也对volatile有了一定基础的认识,更多高级的技巧方丈建议看官可以取阅读一下JDK源码,看看Oracle Java小组的大神们都是如何将这个关键字用得出神入化的!
方丈全栈版权所有,转载请注明出处,如有盗用,后果自负!
最后一面挂在volatile关键字上,面试官:重新学学Java吧!的更多相关文章
- 【JAVA秒会技术之秒杀面试官】秒杀Java面试官——集合篇(一)
[JAVA秒会技术之秒杀面试官]秒杀Java面试官——集合篇(一) [JAVA秒会技术之秒杀面试官]JavaEE常见面试题(三) http://blog.csdn.net/qq296398300/ar ...
- 如何写出面试官欣赏的Java单例
单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例. 今天我们不谈单例模式的用途,只说一说如果在面试的时候面试官让你敲一段代码 ...
- 8年经验面试官详解 Java 面试秘诀
作者 | 胡书敏 责编 | 刘静 出品 | CSDN(ID:CSDNnews) 本人目前在一家知名外企担任架构师,而且最近八年来,在多家外企和互联网公司担任Java技术面试官,前后累计面试了有两三 ...
- 面试官刁难:Java字符串可以引用传递吗?
老读者都知道了,六年前,我从苏州回到洛阳,抱着一幅"海归"的心态,投了不少简历,也"约谈"了不少面试官,但仅有两三个令我感到满意.其中有一位叫老马,至今还活在我 ...
- 面试官:关于Java性能优化,你有什么技巧
通过使用一些辅助性工具来找到程序中的瓶颈,然后就可以对瓶颈部分的代码进行优化. 一般有两种方案:即优化代码或更改设计方法.我们一般会选择后者,因为不去调用以下代码要比调用一些优化的代码更能提高程序的性 ...
- 哪些问题是面试官经常问Java工程师的问题 ? --- 转自quora
Which are the frequently asked interview questions for Java Engineers ? Vivek Vermani, www.buggybrea ...
- 面试官问我“Java中的锁有哪些?以及区别”,我跪了
在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级 ...
- 腾讯面试官问我Java中boolean类型占用多少个字节?我说一个,面试官让我回家等通知
本文首发于微信公众号:程序员乔戈里 什么是boolean类型,根据官方文档的描述: boolean: The boolean data type has only two possible value ...
- Vertx上传 官网Demo Java版
package io.vertx.example.web.upload; import io.vertx.core.AbstractVerticle; import io.vertx.example. ...
随机推荐
- (数据科学学习手札84)基于geopandas的空间数据分析——空间计算篇(上)
本文示例代码.数据及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 在本系列之前的文章中我们主要讨论了g ...
- 苏浪浪 201771010120《面向对象程序设计(java)》第六章学习总结
第五章 主要学习OOP另一个部分----继承,继承使程序员可以使用现有的类,并根据需要进行修改.这是Java程序设计中的一个基础设计. 1.类.超类和子类: (1) 已有类称为:超类(supercla ...
- vue2.0 axios前后端数据处理
目前主流的 Vue 项目,都选择 axios 来完成 ajax 请求,而大型项目都会使用 Vuex 来管理数据. 前言: 使用 cnpm 安装 axios cnpm install axios -S ...
- eclipse——Error exists in required project Proceed with launch?
运行java文件时报错: Error exists in required project Proceed with launch? 报错截图: 问题参生原因:开始Buildpath了一个jar ...
- [PHP插件教程]001.Pear包管理器
PEAR是PHP扩展与应用库(the PHP Extension and Application Repository)的缩写.它是一个PHP扩展及应用的一个代码仓库. 简单地说,PEAR之于PHP就 ...
- 离散数学 II(最全面的知识点汇总)
离散数学 II(知识点汇总) 目录 离散数学 II(知识点汇总) 代数系统 代数系统定义 例子 二元运算定义 运算及其性质 二元运算的性质 封闭性 可交换性 可结合性 可分配性 吸收律 等幂性 消去律 ...
- Vue拖拽交换数据(非插件)
HelloWorld.vue 文件 <template> <div class="hello"> <h1>{{ msg }}</h1> ...
- python九九乘法表程序代码
按照c语言的思路来考虑python的,方法很简单,直接运用双重循环即可,本代码为了代码量少采用的是while嵌套双循环. 取两个随机变量 (1)i和j都从1开始(因为表中最小数值为1) (2)i控制第 ...
- HFish开源蜜罐搭建
简介 Hfish是一款开源的蜜罐,包含了多种仿真服务,如:redis.ssh.telnet.web服务等,支持单机部署.docker部署.集群部署等形式.不属于高交互蜜罐的范畴,只是用来体验一把.放在 ...
- JAVASE(十二) Java常用类: 包装类、String类、StringBuffer类、时间日期API、其他类
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 1.包装类 1 .1 八个包装类 1. 2 基本数据类型,包装类,String者之间的转换 2. ...