多线程(7)- volatile关键字
前言
volatile关键字扩展:
概念
1、初识volatile
-
volatile关键字只能修饰类变量和实例变量、对于方法参数、局部变量、以及实例常量、类常量都不能进行修饰。
-
一段代码
public class VolatileFoo {
private static final int MAX = 5;
private static int x = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
int local_value = x;
while (local_value < MAX) {
if (local_value != x) {
System.out.println(Thread.currentThread().getName()+" local_value =" + local_value + " x = " + x);
local_value = x;
}
}
}, "reader").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
while (x < MAX) {
System.out.println(Thread.currentThread().getName()+" x change = " + ++x);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "writer").start();
}
}
输出结果:
writer x change = 1
writer x change = 2
writer x change = 3
writer x change = 4
writer x change = 5
- 修改类变量x为volatile修饰
private volatile static int x = 0;
输出结果:
writer x change = 1
reader local_value =0 x = 1
writer x change = 2
reader local_value =1 x = 2
writer x change = 3
reader local_value =2 x = 3
writer x change = 4
reader local_value =3 x = 4
writer x change = 5
reader local_value =4 x = 5
Process finished with exit code 0
2、CPU Cache模型
-
计算机中所有运算是CPU的寄存器完成的,CPU指令执行过程中涉及数据的读和写操作,访问的地方只能是计算机主存(RAM)
-
CPU发展频率不断提升,主存由于制造工艺及成本限制,速度没有很大突破,CPU与主存速度差距在上千倍,极端差距在上万倍
-
基于上述原因,现在在CPU和主存之间增加缓存,目前最高级别是三级缓存,最靠近PCU的缓存称为L1,然后依次是L2,L3和主内存。
-
由于程序指令和程序数据的行为和热点分步差异很大,因此L1又被划分为L1i(instruction)和L1d(data)这两种专门用途的缓存。
-
Cache Line可以认为是CPU Cache中的最小缓存单位、目前主流CPU Cache的Cache Line大小都为64字节。
CPU Cache交互
2、CPU缓存一致性问题
-
由于CPU Cache的出现,极大提高了CPU的吞吐能力,但也产生了缓存缓存不一致问题。
-
比如i++ 操作,在程序运行过程中,首先需要将主内存中的数据复制一份到CPU Cache中,那么CPU寄存器在进行数值计算的时候就直接到Cache中读取和写入,整个过程结束之后再将Cache中的数据刷新到主存当中,过程如下:
- 读取主内存的i到CPU Cache中
- 对i进行+1操作
- 将结果写回到Cache中
- 将数据刷新到主存中
-
在单线程中不会有问题,但是在多线程情况下,每个线程都有自己的工作内存(本地内存,对应CPU中的Cache),变量i会在多个线程的本地内存中都存在一个副本,如果同时有两个线程进行i++操作,假设i的初始值=0、每个线程都从主存中获取i的值存入CPU Cache中,然后经过计算再写入主内存,可能i经过两次i++的结果还是1这就是典型的缓存不一致问题。
-
解决方式:
- 通过总线加锁的方式
- 通过缓存一致性协议
- 缓存一致性协议:Intel的MESI协议
- 读取操作;不做任何处理、只是将Cache中的数据读取到寄存器
- 写入操作;发出信号通知其他CPU将该变量的Cache line置为无效状态,其他CPU在进行该变量读取的时候不得不到主内存中再次获取。
4、Java内存模型JMM(Java Memory Mode)
- JVM采用内存模型机制屏蔽各个平台和操作系统之间内存访问的差异,以实现让Java程序在各个平台达到一致的内存访问效果.比如C语言中的整形变量,在某些平台占用两个字节,某些占用四个字节,Java则是在任何平台下,int类型就是四个字节,这就是所谓一致内存访问效果。
- 共享变量存储于主内存中,每个线程都可以访问
- 每个线程都有私有的工作内存或者称为本地内存
- 工作内存只存储该线程对共享变量的副本
- 线程不能直接操作主内存,只有先操作了工作内存之后才能写入主内存
- 工作内存和Java内存模型一样也是一个抽象的概念,它其实并不存在,它涵盖了缓存,寄存器,编译器优化以及硬件等。