Java多线程基础(一)---volatile关键字介绍
  TEZNKK3IfmPf 2023年11月14日 25 0

1 学习内容

  1. 初识volatile关键字
  2. 机器硬件CPU
  3. Java内存模型
  4. CPU缓存一致性问题

2 具体内容

2.1 初识volatile关键字
启动两个线程,一个线程负责对变量进行修改,一个变量负责对变量输出,代码示例如下:

/**
 * 开启两个线程,一个进行读操作,一个线程负责写数据
 * @author kangna
 *
 */
public class VolatilFoo {
	// init_value 最大值
	final static int Max = 5;
	//init_value 初始值
	static int init_value = 0;
	
	public static void main(String args[]){	
		/**
		 * 启动一个Reader线程,当发现init_value 和 local_value 不同时,则输出  init_value 被修改的信息
		 */
		new Thread(()->
		{
			int localValue = init_value;
			while(localValue < Max) {
				if(init_value != localValue){
					System.out.printf("the init_value is update to [%d] \n", init_value);
					localValue = init_value;
				}
			}
	    }, "Reader").start();
		
		/**
		 * 启动一个Update线程,用于对init_value的修改,当local_value>=5时退出生命周期
		 */
		new Thread(()->
		{
			int localValue = init_value;
			while(localValue  < Max){
				System.out.printf("the init_value will be change to [%d] \n", ++localValue);
				init_value = localValue;
				// 短暂
				try {
					TimeUnit.SECONDS.sleep(2);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "update").start();
	}
}

运行结果:
Java多线程基础(一)---volatile关键字介绍
从控制台的输出我们可以看出,Reader线程根本没有感知到init_value的变化,而进入了死循环,我使用volatile关键字,然后看运行结果

static volatile int init_value = 0;

输出结果:
Java多线程基础(一)---volatile关键字介绍
这下好了,看来是volatile关键字发挥了作用。

2.2 机器硬件CPU

在计算机中所有的运算操作都是由CPU的寄存器完成的,CPU指令的执行需要涉及数据的读和写操作,CPU所能访问的所有数据只能是计算机的主存(通常指RAM),虽然CPU的发展频率不断地上升,但受制于制作工艺以及成本的限制,计算机内存访问速度上并没有多大的突破,因此CPU的处理速度和内存的访问速度之间的差距是越来越大,几何级别的差距。

2.2.1 CPU Cache模型

由于两边速度的不对等,通过传统的FSB(前端总线)直连内存的访问方式很明显会导致内存资源受限,降低CPU整体的吞吐量,于是就有了在内存和CPU之间架设缓存的设计,现在缓存数量增加到了3级,最靠近CPU的缓存为L1,然后依次是L2,L3和主内存。由于程序指令和程序数据的行为和热点分布差异很大,因此L1 Cache又划分成L1i(i为instruction)和L1d(d是data的首字母)这两种有各专门用途的缓存,CPU Cache又是由很多个Cache Line 构成的,Cache Line可以认为是CPU Cache最小缓存单位,目前主流CPU Cache的Cache Line大小都是64字节。
Java多线程基础(一)---volatile关键字介绍

  • Cache的出现是为了解决CPU直接访问内存效率低下的问题,程序在运行的过程中, 会将运算所需要的数据从主存复制一份到CPU Cache中,这样CPU对数据的操作可以直接对CPU Cache中的数据进行读和写,当运算结束后,再将CPU Cache中的最新数据刷新到主内存中,CPU通过直接访问Cache的方式替代直接访问主存的方式极大的提高了CPU的吞吐能力,有了CPU Cache之后,整体的CPU和主内存之间交互的架构大致如图。
    Java多线程基础(一)---volatile关键字介绍

2.2.2 CPU缓存一致性问题

缓存的出现极大地提高了CPU的吞吐能力,但是同时也引入了缓存不一致的问题,比如 i++这个操作,在程序运行过程中,首先需要将主内存中数据复制一份到CPU Cache中,那么CPU寄存器在进行数据计算的时候就直接到Cache中读取和写入,当整个过程结束后再将Cache中的数据刷新到主存中。

具体过程如下:

(1)读取主内存的 i 到CPU Cache中
(2)对 i 进行加 1 操作
(3)将结果写回到CPU Cache
(4)将数据刷新到主内存中

这个 i++操作在单线程下不会出现任何问题,但是在多线程情况下就会有问题,每个线程都有自己的工作内存(本地内存,对应于CPU中的Cache),变量 i 会在多个线程的本地内存中都有一个副本。如果同时有两个线程执行 i++ 操作,假设 i 的初始值初始值为 0 ,每一个线程都从主内存中取出 i 的值存入CPU Cache中,然后经过计算再写入主内存中,很有可能 i 在经过了两次自增运算后结果还是 1 ,这就是缓存不一致问题。

为了解决缓存不一致问题,通常有两种解决方式:

  • 通过总线加锁的方式

  • 通过缓存一致性协议

如果采用总线加锁的方式,则会阻塞其他CPU对其它组件的访问,从而使得只有一个CPU能够访问这个变量的内存,然而这种方式效率低下,于是产生了第二种方式通过缓存一致性协议的方式解决不一致问题,如图

Java多线程基础(一)---volatile关键字介绍

在缓存一致性协议中出名的是MESI协议,MESI协议保证了每一个缓存中使用的共享变量都是一致的,大致思想是:当CPU在操作Cache中的数据时,如果发

现该变量是一个共享变量,也就是说在其他的CPU Cache中也存在一个副本,那么进行如下操作:

  • 读取操作,不做任何处理,只是将Cache中的数据读取到寄存器

  • 写入操作,发出信号通知其它CPU将该变量的Cache Line置为无效状态,其它

CPU在进行该变量读取的时候不得不到主内存中再次获取。

2.3 Java内存模型

Java内存模型(Java Memory Mode,JMM)指定了Java虚拟机如何与计算机的主内存(RAM)进行工作,Java内存模型决定了一个线程对共享变量的写入何时对其它线程可见,Java内存模型抽象了线程和主内存之间的抽象关系

  • 共享变量存储于主内存中,每个线程都可以访问
  • 每个线程都有自己的本地内存或者称为工作内存
  • 工作内存中只能存储线程共享变量的副本
  • 线程不能直接操作主内存,只有先操纵了工作内存之后才能写入内存
    工作内存和Java内存模型一样也是一个抽象概念,它其实并不存在它涵盖了缓存、寄存器、编译器优化以及硬件等。
    Java多线程基础(一)---volatile关键字介绍
    Java内存模型是一个抽象的概念,其与计算机硬件的结构并不完全一样。比如计算机物理内存不会存在栈内存和堆内存的划分,无论是堆内存还是栈内存都会对应到物理的主内存,当然也有一部分堆栈内存数据可能会存入CPU Cache寄存器中,下图为Java内存模型与CPU硬件架构交互图。
    Java多线程基础(一)---volatile关键字介绍
    我们再来看一个图
    Java多线程基础(一)---volatile关键字介绍

Java内存模型规定了一个线程如何,及何时可以看到由其他线程修改过后的共享变量的值,以及在必须时如何同步的访问共享变量。

:动态的分配内存大小,生存期也不必事先告诉编译器,运行时动态分配,Java的垃圾收集器会自动收集这些不再使用的数据,由于是动态分配所以他的存取速度有点慢。
:栈的存取速度比堆快,速度仅次于计算机中的寄存器。栈的数据共享,存放基本数据类型和对象句柄,存在于栈中数据的大小和生命周期是确定的,缺乏一定的灵活性。

Java内存要求调用栈和本地变量存放在线程栈上(Thread Stack),对象存在于堆上。一个本地变量也可能是一个指向本地的应用,应用的本地变量是存放于线程栈上的,对象存放于堆上。如果两个线程同时调用了同一个对象,那么这两个线程可以同时访问对象的成员变量(两个线程拥有对象成员变量的私有拷贝)。

当同一个数据被分别存储到计算机的各个内存区域时,势必会导致多个线程在各自的工作内存看到的数据有可能是不一样,在Java语言中如何保证不同线程对某个共享变量的可见性?请听下回详解。

3总结

  1. 引入了volatile关键字
  2. 介绍了CPU模型
  3. CPU缓存一致性解决方案
  4. Java内存模型
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月14日 0

暂无评论

推荐阅读
TEZNKK3IfmPf