1.什么是缓存对齐

  当前的电脑中,数据存储在磁盘上,可以断电保存,但是读取效率较低。不断电的情况下,数据可以在内存中存储,相对硬盘效率差不多是磁盘的一万倍左右。但是运算时,速度最快的是直接缓存在CPU中的数据。CPU有三级缓存分别是L1,L2,L3三级,CPU访问速度大概是内存的100倍。

1.1CPU结构

  对于一台电脑,其主板可以支持多少个CPU插槽,称为CPU个数。对于一颗多核CPU,单片CPU上集成的处理核心称为CPU核数。对于每个核心,可以给每个核设置两组寄存器,两组pc。

  CPU结构如上图所示(图片来自网络),对于一块CPU,可以有多个处理核心。每个核心内有自己的L1,L2缓存,多个核心共用同一个L3缓存。但一个电脑如果有多个CPU插槽,各个CPU有自己的L3。对于一个CPU核心来说,每个核心都有ALU,逻辑运算单元。负责对指令进行计算。Register 寄存器,记录线程执行对应的数据。PC:指令寄存器,记录线程执行到了哪个位置。里面存的是指令行数。通俗讲,就是记录线程执行到了哪一行指令(代码在进入CPU运行前,会被编译成指令)了。

  线程在执行的时候,将当前线程对应的数据放入寄存器,将执行行数放到指令寄存器,然后执行过一个时间片后,如果线程没有执行完,将数据和指令保存,然后其他线程进入执行。一个ALU对应多个PC|registers的时候(所谓的四核八线程)。一般来说,同一个CPU核在同一个时间点,只能执行同一个线程,但是,如果一个核里面有两组寄存器,两个pc。那么就可以同时执行两组线程,在切换线程的时候,没必要再去等待寄存器的数据保存和数据载入。直接切换到下一组寄存器就可以。这就是超线程

1.2缓存对齐

  CPU到内存之间有很多层的内存,如图所示,CPU需要经过L1,L2,L3及主内存才能读到数据。从主内存读取数据时的过程如下:

  当我左侧的CPU读取x的值的时候,首先会去L1缓存中去找x的值,如果没有,那么取L2,L3依次去找。最后从主内存读入的时候,首先将内存数据读入L3,然后L2最后L1,然后再进行运算。但是读取的时候,并不是只读一个X的值,而是按块去读取(跟电脑的总线宽度有关,一次读取一块的数据,效率更高)。CPU读取X后,很可能会用到相邻的数据,所以在读X的时候,会把同一块中的Y数据也读进来。这样在用Y的时候,直接从L1中取数据就可以了。

  读取的块就叫做缓存行,cache line 。缓存行越大,局部性空间效率越高,但读取时间慢。缓存行越小,局部性空间效率越低,但读取时间快。目前多取一个平衡的值,64字节。

  然后,如果你的X和y在同一块缓存行中,且两个字段都用volatile修饰了,那么将来两个线程再修改的时候,就需要将x和y发生修改的消息高速另外一个线程,让它重新加载对应缓存,然而另外一个线程并没有使用该缓存行中对应的内容,只是因为缓存行读取的时候跟变量相邻,这就会产生效率问题。

  解决起来也简单,我们将数据中的两个volatile之间插入一些无用的内存,将第二个值挤出当前缓存行,那么执行的时候,就不会出现相应问题了。提高代码效率。

2.缓存对齐在java中实现

  在java中,jdk一些涉及到多线程的类,有时候会看到类似于public volatile long p1,p2,p3,p4,p5,p6,p7;这样的代码,有的就是做的缓存行对齐。

  我们设计一个实验去验证缓存行对齐的导致的性能问题,及相关的解决后的效率问题。具体代码见第三小节。这里的思路是,首先,我们写一个类T,这个类里面有一个用volatile修饰的long属性的值,这个值占用8个字节。然后声明一个静态数组,包含两个元素,分别T的两个对象。然后开启两个线程,让两个线程分别给数组的第一个值和第二个值赋值,执行一百万次,看执行的耗时。

  这个时候,代码执行的时候如1.2的图中所示,假设数组中第一个值为X,第二个值为Y。左侧框内为第一个线程,执行修改X值的操作,右侧框内为第二个线程,修改Y的值。因为两个值在同一个缓存行中,所以在X值在读取的时候,同时将X值和Y值一起读入缓存。第二个线程只修改Y的值,但是同样将XY全部读入缓存。线程1中X值发生修改后,第二个线程中的X值需要进行更新。而线程2修改Y的值后也需要同样的操作,但是这个更新不是必要的,而且会影响执行的效率。

  解决方法是:我们给第T的long值之前加入8个long值,这样Y值就会被挤到其他缓存行,这样彼此修改的时候就不会产生干扰,提高代码执行效率。

  下面是具体验证的代码,其中在没有加入父类的时候,是相互干扰时的执行耗时。第二个是加入父类后,不再干扰时的耗时,执行后可以看出,第二套代码在执行的时候,代码要优于第一套代码的执行。

3.缓存对齐的代码实现

 public class T01_CacheLinePadding {
private static class T{
public volatile long x = 0L;
}
public static T[] orr = new T[2];
static {
orr[0]= new T();
orr[1]= new T();
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 1000_000L; i++) {
orr[0].x = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 1000_000L; i++) {
orr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime()-start)/100_000);
}
}
 package msb;
/**
* 缓存行对齐问题代码
* @author L Ys
*
*/
public class T02_CacheLinePadding {
private static class Padding{
public volatile long p1,p2,p3,p4,p5,p6,p7;
}
private static class T extends Padding{
public volatile long x = 0L;
}
public static T[] orr = new T[2];
static {
orr[0]= new T();
orr[1]= new T();
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 1000_000L; i++) {
orr[0].x = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 1000_000L; i++) {
orr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime()-start)/100_000);
}
}

Java多线程_缓存对齐的更多相关文章

  1. Java多线程_生产者消费者模式2

    在我的上一条博客中,已经介绍到了多线程的经典案列——生产者消费者模式,但是在上篇中用的是传统的麻烦的非阻塞队列实现的.在这篇博客中我将介绍另一种方式就是:用阻塞队列完成生产者消费者模式,可以使用多种阻 ...

  2. Java多线程_复习(更新中!!)

    java多线程的常见例子 一.相关知识: Java多线程程序设计到的知识: (一)对同一个数量进行操作 (二)对同一个对象进行操作 (三)回调方法使用 (四)线程同步,死锁问题 (五)线程通信 等等 ...

  3. 1.java多线程_实现线程的两种方式

    1.java多线程基本知识 1.1.进程介绍 不管是我们开发的应用程序,还是我们运行的其他的应用程序,都需要先把程序安装在本地的硬盘上.然后找到这个程序的启动文件, 启动程序的时候,其实是电脑把当前的 ...

  4. Java多线程_并发容器ConcurrentHashMap/CopyOnWriteArrayList/CopyOnWriteArraySet

    ConcurrentHashMap         HashMap是线程不安全的,可以使用Collections.synchronizedMap(map)把一个不安全的map变成安全的,但是这里可以直 ...

  5. Java多线程_线程池

    作用我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为 ...

  6. Java多线程_同步工具CyclicBarrier

    CyclicBarrier概念:CyclicBarrier是多线程中的一个同步工具,它允许一组线程互相等待,直到到达某个公共屏障点.形象点儿说,CyclicBarrier就是一个屏障,要求这一组线程中 ...

  7. Java多线程_同步工具CountDownLatch

    概念:CountDownLatch是多线程里面一个类似于计数器的高级同步工具,它的初始值代表线程的数量,当一个线程完成了任务后,CountDownLatch的值就减1,当值为0的时候,代表所有线程完成 ...

  8. Java多线程_生产者消费者模式1

    生产者消费者模型       具体来讲,就是在一个系统中,存在生产者和消费者两种角色,他们通过内存缓冲区进行通信,生产者生产消费者需要的资料,消费者把资料做成产品.生产消费者模式如下图.(图片来自网络 ...

  9. java ->多线程_线程同步、死锁、等待唤醒机制

    线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码.程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的. l  我们通过一个案例,演示线 ...

随机推荐

  1. 10个Vue开发技巧助力成为更好的工程师·二

    优雅更新props 更新 prop 在业务中是很常见的需求,但在子组件中不允许直接修改 prop,因为这种做法不符合单向数据流的原则,在开发模式下还会报出警告.因此大多数人会通过 $emit 触发自定 ...

  2. 读懂操作系统之快表(TLB)原理(七)

    前言 前不久.我们详细分析了TLB基本原理,本节我们通过一个简单的示例再次叙述TLB的算法和原理,希望借此示例能加深我们对TLB(又称之为快表,深入理解计算机系统(第三版)又称之为翻译后备缓冲区)的理 ...

  3. 扫描PDF417崩溃的原因找到:手机摄像头分辨率低

    换孩子姥姥华为手机解决了. 能扫pdf417码了

  4. SpringCloud Bus 动态刷新全局广播和定点通知

    全局广播 前提: 先具备良好的 RabbitMQ 环境 1. 演示广播效果,增加复杂度,再以3355为模板再制做一个3366模块 <!--pom.xml--> <?xml versi ...

  5. 谁来教我渗透测试——黑客应该掌握的Windows基础

    今天我们看看作为一个黑客对于Windows应该掌握哪些基础知识,主要内容包含以下四个方面: 系统目录.服务.端口和注册表: 黑客常用的DOS命令及批处理文件的编写: 黑客常用的快捷键,以及如何优化系统 ...

  6. PHP password_hash() 函数

    password_hash() 函数用于创建密码的散列(hash) PHP 版本要求: PHP 5 >= 5.5.0, PHP 7高佣联盟 www.cgewang.com 语法 string p ...

  7. PHP round() 函数

    实例 对浮点数进行四舍五入:高佣联盟 www.cgewang.com <?php echo(round(0.60) . "<br>"); echo(round(0 ...

  8. 小甲鱼零基础汇编语言学习笔记第二章之寄存器(CPU工作原理,CPU内部通讯)

    这一章主要介绍了CPU中的重要器件——寄存器,整个系列通篇是以8086CPU作为探讨对象,其它更高级的CPU都是在此基础之上进行的升级.   1.一个典型的CPU是由运算器.控制器.寄存器等器件组成, ...

  9. luogu 3188 [HNOI2007]梦幻岛宝珠

    LINK:梦幻岛宝珠 时隔多日 我再次挑战这道题.还是以失败告终. 我觉得这一道背包真的有点难度 这是一个数量较少 但是价值和体积较大的背包. 通常的01背包 要不就是体积小 要么是价值小 但这道题给 ...

  10. source命令用法:source FileName

    转自https://zhidao.baidu.com/question/59790034.html  写得很清楚,就直接搬过来了备忘 作用:在当前bash环境下读取并执行FileName中的命令. 注 ...