说到volatile,一些参加过面试的同学对此肯定不陌生。

它是面试官口中的常客,但是平时的编码却很少打照面(起码,我是这样的)。

最近的面试,我也经常会问到volatile相关的问题,比如volatile和sychronized的区别volatile的使用场景volatile的实现原理等等。

问这些问题其实主要还是考察多线程、锁等方便的知识储备。虽然volatile在我们日常编码使用不多,但是他的实现思想以及背后牵扯的一些概念还是值得我们学习和思考的。

举例

首先我们来看一个例子


public class VolatileExample extends Thread { private static boolean flag = false; public void run() {
while (!flag) {
}
} public static void main(String[] args) throws Exception {
new VolatileExample().start();
Thread.sleep(100);
flag = true;
}
}

这段代码并不长。

  • 声明了一个布尔类型的静态变量flag,初始值为false;

  • main函数中启动了一个线程VolatileExample,集成自Thread类;

  • 除去VolatileExample线程,当然还有一个主线程main;

  • 主线程sleep 100毫秒,并在其后置flag为true;

那么,你觉得运行main会怎样

  • 顺利跑完所有代码,结束?

  • 程序一直在等待,不会结束?

我们看下运行的结果

从结果可以发现,主线程运行结束后,界面中左边的红点并没有消失,表示程序还没有结束。

这是因为启动的VolatileExample线程读取到的flag一直都是false。

虽然主线程中flag已经被赋值为true,但是VolatileExample线程却视而不见,这是为什么呢,这就涉及到一个可见性的问题。

可见性

这里假设大家都是对线程以及锁等知识有一定的了解

sychronized我们应该都用过或者了解过,这是为了在多线程环境下对共享资源添加一个标识,用于锁住共享资源,防止多线程同时进入对数据操作产生不一致的现象。

我们平时说的加锁其实表面上是为了达到一种互斥的效果,也就是对于同时存在的线程A和线程B,如果这时候线程A或者锁,则线程B只能在旁边乖乖的等,这就是一种互斥,有你没我,有我没你!

但是加锁的本质是解决了可见性的问题。具体先不说这个问题,我们先结合上面的例子来说说可见性的问题。后面在结合可见性可能更好理解加锁的本质。

在java内存模型中,是分为主内存工作内存两块的。

主内存,主要是存储各个线程都会用到共享变量等。

对于每个线程都有自己的一个存储变量的地方,就是工作内存。各个线程之间的工作内存是相互独立的,不可见的。

到这里,你可能已经知道上面例子的程序为什么一直出于运行的状态没有终止了。没错,VolatileExample线程读取的flag值是自己线程中存储在工作内存中的值,主线程中的flag值虽然更新了,但是对于VolatileExample是不可见、无感知的。

所以,这时候volatile关键字就排上用场了。我们在flag变量钱添加volatile关键字修饰。再次运行程序

效果显而易见,主线程和VolatileExample线程一同结束。

这是因为使用volatile关键字修饰,该变量是强制要求从主内存读以及往主内存写,这样保证各个线程在操作这个变量的值时,都是最新鲜的数据。

这时候,我们再来回顾刚刚说到加锁的本质是解决可见性问题。

volatile是强制让所有线程都从一个地方操作共享资源,使得原来是从每个线程的副本中读取变量变为从主内存读取变量,从而解决了可见性问题。

而sychronized也是解决了可见性问题,它不允许同一时间有两个线程操作同一共享资源,因为其无法保证可见性。所以其通过独占互斥的方式,保证自己执行完之后才会有下一个执行,这样每次只有一个线程占有资源,也是间接的解决了可见性的问题。

说到这,就不得不提另外一个问题——原子性

原子性


private int num = 0; num++;

上面的代码符合原子性么?

显然不符合,假如现在有线程A和线程B两个线程,

线程A读取num=0,准备执行num++的操作,

但是还没有执行“++”操作之前,线程B读取num的值,此时值为0,之后也执行num++的操作,

最后A和B两个线程最终得到的值都是1,

这是不符合原子性的。

通过如下的sychronized的修饰就符合原子性了


sychronized(this) { num++; }

其道理还是因为sychronized的独占性。

那么volatile是否可以保证原子性呢

答案是否定的。道理和上面没有加sychronized的描述是一样的。

线程A还有执行完num++后,线程B也来访问num值,得到的是0,然后执行num++,最终还是两个线程得到的值都是1。

那么volatile有哪些使用场景呢。

volatile的适用场景

  • volatile是在synchronized性能低下的时候提出的。如今synchronized的效率已经大幅提升,所以volatile存在的意义不大。

  • 如今非volatile的共享变量,在访问不是超级频繁的情况下,已经和volatile修饰的变量有同样的效果了。

  • volatile不能保证原子性,这点是大家没太搞清楚的,所以很容易出错。

  • volatile可以禁止重排序(sychronized也是可以的)。

其实与之相关概念还有重排序、happens-before,as-if-serial等等,限于篇幅,不再详述。

如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!如果您想持续关注我的文章,请扫描二维码,关注JackieZheng的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

并发和多线程-说说面试长提平时少用的volatile的更多相关文章

  1. 并发和多线程-八面玲珑的synchronized

    上篇<并发和多线程-说说面试常考平时少用的volatile>主要介绍的是volatile的可见性.原子性等特性,同时也通过一些实例简单与synchronized做了对比. 相比较volat ...

  2. java并发与多线程面试题与问题集合

    http://www.importnew.com/12773.html     https://blog.csdn.net/u011163372/article/details/73995897    ...

  3. Android并发编程 多线程与锁

    该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,如果能给各位看官带来一丝启发或者帮助,那真是极好的. 前言 前一篇Android并发编程开篇呢,主要是简单介绍一下线程以及 ...

  4. Java高并发与多线程(四)-----锁

    今天,我们开始Java高并发与多线程的第四篇,锁. 之前的三篇,基本上都是在讲一些概念性和基础性的东西,东西有点零碎,但是像文科科目一样,记住就好了. 但是本篇是高并发里面真正的基石,需要大量的理解和 ...

  5. python高并发和多线程的关系

    “高并发和多线程”总是被一起提起,给人感觉两者好像相等,实则 高并发 ≠ 多线程 多线程是完成任务的一种方法,高并发是系统运行的一种状态,通过多线程有助于系统承受高并发状态的实现.   高并发是一种系 ...

  6. php-fpm和cgi,并发响应的理解以及高并发和多线程的关系

    首先搞清楚php-fpm与cgi的关系 cgi cgi是一个web server与cgi程序(这里可以理解为是php解释器)之间进行数据传输的协议,保证了传递的是标准数据. php-cgi php-c ...

  7. 并发和多线程(四)--wait、notify、notifyAll、sleep、join、yield使用详解

    wait.notify.notifyAll 这三个方法都是属于Object的,Java中的类默认继承Object,所以在任何方法中都可以直接调用wait(),notifyAll(),notify(), ...

  8. 并发和多线程(二)--启动和中断线程(Interrupt)的正确姿势

    启动线程: 从一个最基本的面试题开始,启动线程到底是start()还是run()? Runnable runnable = () -> System.out.println(Thread.cur ...

  9. Python并发编程——多线程与协程

    Pythpn并发编程--多线程与协程 目录 Pythpn并发编程--多线程与协程 1. 进程与线程 1.1 概念上 1.2 多进程与多线程--同时执行多个任务 2. 并发和并行 3. Python多线 ...

随机推荐

  1. 017 在SecureCRT中安装rz小工具

    1.安装yum 2.上传本地的文件进虚拟机 3.注意点 只是属于SecureCRT的命令,同时,在上传的位置是现在所在的位置 4.测试

  2. Python3 - 基础知识、基本了解

    一.Python到底是什么? (抄自 金角大王) 1. Python是一门解释型语言? 我初学Python时,听到的关于Python的第一句话就是,Python是一门解释性语言,我就这样一直相信下去, ...

  3. Java Swing 实时刷新JTextArea,以显示不断append的内容?

    方法一: 在代码中执行完textArea.append("message")后,如果你想让这个更新立刻显示在界面上而不是等swing的主线程返回后刷新,我们一般会在该语句后调用te ...

  4. 【转】一张图解析FastAdmin中的表格列表的功能

     一张图解析FastAdmin中的表格列表的功能 功能描述请根据图片上的数字索引查看对应功能说明. 1.时间筛选器如果想在搜索栏使用时间区间进行搜索,则可以在JS中修改修改字段属性,如 {field: ...

  5. 深入理解 MySQL ——锁、事务与并发控制

    本文首发于vivo互联网技术微信公众号 mp.weixin.qq.com/s/JFSDqI5ya… 作者:张硕 本文对 MySQL 数据库中有关锁.事务及并发控制的知识及其原理做了系统化的介绍和总结, ...

  6. RBF(径向基)神经网络

    只要模型是一层一层的,并使用AD/BP算法,就能称作 BP神经网络.RBF 神经网络是其中一个特例.本文主要包括以下内容: 什么是径向基函数 RBF神经网络 RBF神经网络的学习问题 RBF神经网络与 ...

  7. 4815: [Cqoi2017]小Q的表格 莫比乌斯反演 分块

    (Updated 2018.04.28 : 发现公式效果不好,重新处理图片)国际惯例的题面:看到这两个公式,很多人都会想到与gcd有关.没错,最终的结论就是f(a,b)=f(gcd(a,b))*(a/ ...

  8. Some Formulas.

    目录 计数问题 在一个有\(n\)个点的完全图(complete graph)中有多少棵生成树 排列组合 1. 当 \(C_n^m\) 为奇数时,\((n\&m)==m\) 2. \[\sum ...

  9. node+koa2 向页面传值方式

    1. router.post('/form',async(ctx,next)=>{ const form = ctx.request.body; console.log('用户名:'+form. ...

  10. C#高级编程9 第16章 错误和异常

    C#高级编程9 第16章 错误和异常 了解这章可以学会如何处理系统异常以及错误信息. System.Exception类是.NET运行库抛出的异常,可以继承它定义自己的异常类. try块代码包含的代码 ...