zoukankan      html  css  js  c++  java
  • jvm-volatile之缓存行

    我们都知道volatile为共享变量提供了可见性,下面就来分析这种可见性是如何实现的。

    在说volatile之前,首先需要引入一个概念:缓存行。为了增加cpu的访存速度,通常会在cpu和内存之间增加多级缓存,如下图,L1、L2都是核心独享的缓存,L3为单个插槽上所有cpu共享的缓存,MainMemory为所有cpu共享。

    根据局部性原理,cpu每次访问主存时都会读取至少一个缓存行的数据(通常一个缓存行为64字节,哪怕读取4字节数据,也会连续读取该数据之后的60字节),这里省略了多级缓存。

    下面来做一个测试,两个线程分别对一个共享int变量进行10000000次++操作,最终的输出大概率小于20000000且大于10000000,这说明JMM对共享变量的更新操作的逻辑大致是,先同步会主存,然后再从主存读取该共享变量。

     1     private int i = 0;
     2     @Test
     3     public void testIPlusPlus() throws InterruptedException {
     4         CountDownLatch countDownLatch = new CountDownLatch(2);
     5         class Task implements Runnable {
     6 
     7             @Override
     8             public void run() {
     9                 for(int j=0; j<10000000; j++) {
    10                     i++;
    11                 }
    12                 countDownLatch.countDown();
    13             }
    14         }
    15         new Thread(new Task()).start();
    16         new Thread(new Task()).start();
    17         countDownLatch.await();
    18         System.out.println(i);
    19     }

    说了这么多,现在来看volatile。

    Java代码: instance = new Singleton();//instance是volatile变量
    汇编代码:

    movb $0x0,0x1104800(%esi);

    lock addl $0x0,(%esp);

    lock指令会锁定共享变量所在的所有缓存行,变量更新完成同步回内存后再释放,这样就会产生一个性能问题,当一个线程更新变量时,其他线程都无法对该变量的相邻变量操作了(因为相邻变量被预读取在同一缓存行)。

    造成这种情况的根本原因在于,volatile锁定了缓存行,故要想优化volatile,可以使用填充空白字节的方法,将多个无关的共享变量存放在多个缓存行中。但这样做并不都是有利的,因为填充空白字节使得对象膨胀。

    注意,要区分缓存(行)、工作内存、主内存,缓存是硬件概念,工作内存、主内存是JMM概念。假如有如下一个类,线程B读取value字段,线程A更新value字段,线程A更新next字段,此时双核cpu中每个核心的缓存可能都会有一个缓存行包含着value、next,线程A更新next的操作会使两个缓存行都失效,此时线程B再次读取value时就需要再次从工作内存中读取了(这就是要注意的地方,由于value只是普通字段,故不需要从主内存读取,又由于cpu缓存失效,故才需要从工作内存中读取,而此时工作内存中的值仍然是旧值)。

    1 class Node{
    2     private int value;
    3     private volatile Node next;
    4 }

     还要注意的是正是由于volatile声明才使得所有与volatile字段相关的缓存行失效,普通字段不会有这种效果。

    参考:http://ifeve.com/volatile/

      http://ifeve.com/disruptor-cacheline-padding/

  • 相关阅读:
    vsftpd的主动模式与被动模式
    Linux环境下vsftpd参数配置
    CentOS下的网络配置文件说明
    第一篇博客,随笔留念
    asp.net xml 增删改操作
    asp.net json 与xml 的基础事例
    linq 之 Distinct的使用
    【P2015】二叉苹果树(树状DP)
    【P2016】战略游戏(贪心||树状DP)
    【P2774】方格取数问题(贪心+最大流,洛谷)
  • 原文地址:https://www.cnblogs.com/holoyong/p/7620542.html
Copyright © 2011-2022 走看看