上一篇:多线程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类型变量:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)