多线程04--线程的可见性

多线程04--线程的可见性,第1张

上一篇:多线程03--synchronized和锁升级_fengxianaa的博客-CSDN博客

1. 可见性

在java中,每一个线程都有一块工作内存,其中存放着主内存中的变量值得拷贝,当线程执行时,它在自己的工作内存区中 *** 作这些变量。

代码:

/**
 * 证明工作内存的存在
 */
class Vo implements Runnable {

    boolean bool = true;
    @Override
    public void run() {
        System.out.println("start......");
        while (bool){

        }
        System.out.println("end......");
    }
}

public class T05_Volatile {

    public static void main(String[] args) throws InterruptedException {
        Vo vo = new Vo();
        new Thread(vo).start();

        Thread.sleep(1000L);
        vo.bool = false;
    }
}
//运行该代码,会发现死循环,“System.out.println("end......");”这句代码永远也不会执行
//这时候只要在bool变量前加上volatile修饰符,程序就能正常结束

volatile 使变量在多个线程中可见,当一个线程修改变量后,强制其他线程到主内存中读取变量值,性能比synchronized强,不会阻塞。但是不具备原子性,不适当的使用,在CPU层面上极有可能造成计算速度降低。

代码:

/**
 * 不适当的使用volatile会造成速度降低
 */
class Demo {
    long a;
//    volatile long a;
}
public class Volatile03 {

    public static void main(String[] args) throws InterruptedException {
        Demo[] arr = new Demo[]{new Demo(),new Demo()};
        long start = System.currentTimeMillis();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100000000; i++) {
                //第1个线程修改数组中的第一个元素
                arr[0].a = i;
            }
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            for(int i =0;i<100000000;i++){
                //第2个线程修改数组中的第二个元素
                arr[1].a = i;
            }
        });
        t2.start();

        t1.join();t2.join();
        System.out.println(System.currentTimeMillis()-start);
    }

以上代码运行时间:220毫秒左右,

对代码进行修改,把变量a加上 volatile,运行时间:3000毫秒左右。

想知道原因,得先弄明白以下几个东西:

CPU缓存

工作内存 本质上就是 CPU缓存 ,是 CPU与内存之间的临时数据区

为什么需要CPU缓存?

  • 解决CPU运行速度与内存读写速度不匹配的矛盾——缓存的速度比内存的速度快多了。
  • CPU往往需要重复处理相同的数据、重复执行相同的指令,如果这部分数据、指令CPU能在CPU缓存中找到,CPU就不需要从内存或硬盘中再读取数据、指令,从而提高运行速度。

CPU缓存分为3级:L1一级缓存、L2二级缓存、L3三级缓存,它们的作用都是作为CPU与主内存之间的高速数据缓冲区,L1最靠近CPU核心;L2其次;L3再次。

一级缓存其实还分为一级数据缓存(Data Cache,L1d-Cache)和一级指令缓存(Instruction Cache,l1i-Cache),分别用于存放数据及指令,两者可同时被CPU访问,减少了CPU多核心、多线程争用缓存造成的冲突,提高处理器性能。

速度方面:L1最快、L2次快、L3最慢;

大小方面:L1最小、L2较大、L3最大。

CPU会先在L1中寻找需要的数据,找不到再去L2,还找不到再去L3,L3都没有那就只能去主内存找了。

缓存行(Cache Line)

CPU从主内存中加载数据到缓存,是以行为单位的,一行64字节,CPU每次从主存中拉取数据时,会把相邻的数据也存入同一个cache line。也就是说,如果CPU计算时需要用到变量a,假设a是long类型,8字节,那么CPU会把a左右挨着的变量共64字节,统统拿过去。

上面代码加了volatile后,速度变慢的原因分析:

数组是一块连续内存,并且我们的数组有两个Demo对象,一共16字节,所以两个线程使用加载时,极有可能在同一cache line,,都在自己的工作内存中有两个Demo对象的副本,虽然线程1 *** 作arr[0],线程2 *** 作arr[1],看似互不影响,但是由于变量都是volatile的,当线程1修改a变量时,线程2中的arr[0].a就无效了,这就导致整个cache line无效,所以每次都要从主内存中取数据。

对代码进行修改:

 long p1,p2,p3,p4,p5,p6,p7;//7个long类型变量,56字节
 volatile long a;
 long p8,p9,p10,p11,p12,p13,p14;//7个long类型变量,56字节

这样变量代码中数据的两个对象,就不可能在同一cache line,所以两个线程互不影响,再次运行:700毫秒左右。

著名的disruptor框架,也是声明很多long类型变量:

  

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/788283.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存