设为首页 加入收藏

TOP

volatile关键字的详解-并发编程的体现(一)
2019-08-24 00:04:40 】 浏览:62
Tags:volatile 关键字 详解 并发 编程 体现

xl_echo编辑整理,欢迎转载,转载请声明文章来源。欢迎添加echo微信(微信号:t2421499075)交流学习。 百战不败,依不自称常胜,百败不颓,依能奋力前行。——这才是真正的堪称强大!!


参考书籍:《Java高并发编程详解》。尊重原创,支持知识付费,以下内容标记有摘抄的为该书内容,如需查看该书的对应知识点,请购买原版书籍。

参考文章列表:


volatile

什么是volatile

volatile和synchronized相比,volatile被称为轻量级锁,并且能实现部分synchronized的语义。它在处理多线程并发的时候主要保证了共享资源的可见性,该功能可以理解为一个线程修改某一个共享变量的时候,另外一个变量可以读到该共享变量的值。

资源无可见性在代码中产生的问题

基于了解程序原理就先上代码观察结果的思想,我们可以通过观察下面这段代码,先对volatile的基本体现有一个了解。

以下代码来自摘抄

public class VolatileFoo {

    final static int MAX = 5;

    static int init_value = 0;
    //static volatile int init_value = 0;

    public static void main(String[] args) {

        new Thread(() -> {
            int localValue = init_value;
            while (localValue < MAX) {
                if (init_value != localValue) {
                    System.out.printf("The init_value is update to [%d]\n", init_value);
                    localValue = init_value;
                }
            }
        }, "Reader").start();

        new Thread(() -> {
            int localValue = init_value;
            while (localValue < MAX) {
                System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
                init_value = localValue;
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Updater").start();
    }

}

如果我们对volatile没有了解的情况下,我们看到以上代码会觉得这就是实现创建了两个线程不断的去交替输出一个变量的功能。观看代码确实就是localValue不断的变更,直到localValue等于5的时候,while循环才结束。但是我们可以看到以下结果,与这里的结论不符,证明我们的结论有误
图片

实际的输出结果是我们的Updater线程一直在输出,当localValue=5的时候,我们的Updater线程就会结束循环。而我们的Reader线程却一直在执行,并是处于死循环的状态。这里我们可以看到Reader线程读到的localValue的值应该是一直没有改变,也能够明显的看到一个问题,那就是两个线程访问一个变量,Updater修改变量的值后Reader线程并没有获取到这个值的变化。

volatile的初体验

出现以上问题之后,我们可以看到共享变量的可见性它的重要性,解决上面程序的问题其实也比较简单,只需要在上面做一个小修改即可。将init_value使用volatile进行修饰,其他的不变,我们再一次观察输出结果。
图片

通过输出结果我们可以看到,当Updater线程对init_value进行了改变之后,我们的Reader线程有效的观察到了这个变量的变化,并且跟着输出了Reader线程观察到init_value的结果。和上面不一样的在于,这段代码都能有效的结束程序

深入了解volatile,并有效掌握实现的方式还需要去了解->CPU硬件的运行和JMM内存模型。CPU与我们程序运行之间的关系是怎么样的,底层是怎么出现这些错误的使我们了解volatile必不可少需要掌握的要点。

我们编写的程序与CPU执行的关系

在我们的程序运行中,我们一个程序被CPU有效执行的一个过程要经过写入硬盘,内存加载,CPU访问执行。,硬盘、内存和CPU之间又有很大的区别。

  • 硬盘:存储资料和软件等数据的设备,有容量大,断电数据不丢失的特点。也被人们称之为“数据仓库”。我们编写的程序就是存储在硬盘里面
  • 内存:1. 负责硬盘等硬件上的数据与CPU之间数据交换处理;2. 缓存系统中的临时数据。3. 断电后数据丢失。可以称为他就是硬盘和CPU之间的桥梁,并且我们的程序编写完成存储到硬盘中之后,开始执行就会被加载进入内存,并等待CPU对内存进行寻址操作。
  • CPU:中央处理单元(Cntral Pocessing Uit)的缩写,也叫处理器,是计算机的运算核心和控制核心。执行我们编写的代码CPU只是接收到执行指令,然后对内存进行寻址操作。

硬盘、内存和CPU的存取速度是递增的,内存比硬盘要快很多,但是CPU又比内存块很多倍,CPU的存取速度快到内存都跟不上,所以在CPU和内存之间出现了一个新的东西,那就是CPU Cache。cache的容量远远小于主存,因此出现cache miss在所难免,这也是我们为什么会出现数据问题的关键所在。

CPU cache结构及cache操作数据导致数据不一致的问题

CPU中cache的结构我们可以打开任务管理器,点击性能即可以看到。多核CPU的结构与单核相似,但是多了所有CPU共享的L3三级缓存。在多核CPU的结构中,L1和L2是CPU私有的,L3则是所有CPU核心共享的。
图片

在我们的系统中,由于短板效应,导致即时我们CPU速度再快也没有办法发挥它的能力。由于内存的读取速度远低于CPU,所以导致我们的程序执行速度,被内存限制。但是当cache出现之后完全改变了这个情况,它极大的增大了CPU的吞吐量。CPU只需要到cache中进行读取和写入操作即可,cache会在之后将结果同步到内存。但是当多线程情况下就会出现问题,每个线程都有自己的工作内存,本地内存,对应CPU中Cache。当多个线程同时操作一个变量的时候,都会进行读取到CPU Cache当中,然后在同步会主内存,这样就会导致数据结果不一致。也就是我们平时看到的,多线程结果与预期不一致的问题。

Java内存模型

Java的内存模型(Java Memory Mode)制定了Java虚拟机如何与计算机的主存进行工作,理解Java内存模型对于编写并发程序非常重要。在CPU cache当中我们使用文字描述了多线程情况下出现结果不一致情况,这里我们可以通过Java内存模型的图解来更直观的看到这个情况是怎么出现的。
图片

图中线程1的工作内存和线程2的工作内存就是我们上面描述的当有多个线程操作一个变量时,每个线

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇面试必问的Spring IOC详解 下一篇雪花算法(04)机器信息

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目