最后一面挂在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吧!的更多相关文章

  1. 【JAVA秒会技术之秒杀面试官】秒杀Java面试官——集合篇(一)

    [JAVA秒会技术之秒杀面试官]秒杀Java面试官——集合篇(一) [JAVA秒会技术之秒杀面试官]JavaEE常见面试题(三) http://blog.csdn.net/qq296398300/ar ...

  2. 如何写出面试官欣赏的Java单例

    单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例. 今天我们不谈单例模式的用途,只说一说如果在面试的时候面试官让你敲一段代码 ...

  3. 8年经验面试官详解 Java 面试秘诀

      作者 | 胡书敏 责编 | 刘静 出品 | CSDN(ID:CSDNnews) 本人目前在一家知名外企担任架构师,而且最近八年来,在多家外企和互联网公司担任Java技术面试官,前后累计面试了有两三 ...

  4. 面试官刁难:Java字符串可以引用传递吗?

    老读者都知道了,六年前,我从苏州回到洛阳,抱着一幅"海归"的心态,投了不少简历,也"约谈"了不少面试官,但仅有两三个令我感到满意.其中有一位叫老马,至今还活在我 ...

  5. 面试官:关于Java性能优化,你有什么技巧

    通过使用一些辅助性工具来找到程序中的瓶颈,然后就可以对瓶颈部分的代码进行优化. 一般有两种方案:即优化代码或更改设计方法.我们一般会选择后者,因为不去调用以下代码要比调用一些优化的代码更能提高程序的性 ...

  6. 哪些问题是面试官经常问Java工程师的问题 ? --- 转自quora

    Which are the frequently asked interview questions for Java Engineers ? Vivek Vermani, www.buggybrea ...

  7. 面试官问我“Java中的锁有哪些?以及区别”,我跪了

    在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级 ...

  8. 腾讯面试官问我Java中boolean类型占用多少个字节?我说一个,面试官让我回家等通知

    本文首发于微信公众号:程序员乔戈里 什么是boolean类型,根据官方文档的描述: boolean: The boolean data type has only two possible value ...

  9. Vertx上传 官网Demo Java版

    package io.vertx.example.web.upload; import io.vertx.core.AbstractVerticle; import io.vertx.example. ...

随机推荐

  1. Java的集合(一)

    转载:https://blog.csdn.net/hacker_zhidian/article/details/80590428 Java集合概况就三个:List.set和map list(Array ...

  2. vscode环境配置(一)——C Program运行

    ctrl + shift +p 打开应用商店 搜索 C/C++  和 Code Runner(一键编译运行)  

  3. web自动化之alert弹窗操作

    from selenium import webdriver from selenium.webdriver.support.wait import WebDriverWait from seleni ...

  4. SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'xxx' already exists

    字面意思 xxx表已存在. 在使用laravel  写同步结构的时候 最好习惯性写个if语句判定是否存在 // 判断数据表是否存在 Schema::hasTable('table'); // 判断数据 ...

  5. 微软Azure IoT驱动数字化变革线上分享会(6月4号)

    微软Azure IoT驱动数字化变革线上分享会(6月4号)   微软作为全球范围内IoT领域的领军者,以微软智能云Azure为基础和核心,推动包括物联网.机器学习.微服务.人工智能等在内的新技术的发展 ...

  6. Azure Kubernetes 服务 (AKS)

    一.首先创建集群 1,注意:一定要选择Kubernets Service(红框处),上面的那一堆虚拟机都没有用, 2,设置好相关属性,集群大小可后面更改节点数,但是节点的大小不可更改 二.登陆集群 在 ...

  7. DQN(Deep Q-learning)入门教程(三)之蒙特卡罗法算法与Q-learning算法

    蒙特卡罗法 在介绍Q-learing算法之前,我们还是对蒙特卡罗法(MC)进行一些介绍.MC方法是一种无模型(model-free)的强化学习方法,目标是得到最优的行为价值函数\(q_*\).在前面一 ...

  8. Chisel3 - model - when

    https://mp.weixin.qq.com/s/YGTXky4wff7LXUphUxISQg   介绍创建模块判断逻辑的when命令.   1. when/elsewhen/otherwise ...

  9. Java实现 LeetCode 802 找到最终的安全状态 (DFS)

    802. 找到最终的安全状态 在有向图中, 我们从某个节点和每个转向处开始, 沿着图的有向边走. 如果我们到达的节点是终点 (即它没有连出的有向边), 我们停止. 现在, 如果我们最后能走到终点,那么 ...

  10. Java实现 LeetCode 507 完美数

    507. 完美数 对于一个 正整数,如果它和除了它自身以外的所有正因子之和相等,我们称它为"完美数". 给定一个 整数 n, 如果他是完美数,返回 True,否则返回 False ...