Java并发——Java内存模型

Java内存模型之JUC底层

  • Java内存模型是什么?
    • 运行在Java虚拟中的线程的线程栈
    • Java虚拟中的堆
  • 计算机的硬件处理数据流程
  • Java控制线程安全
    • Volatitle
    • CAS

Java内存模型是什么?

Java内存模型规范了Java虚拟机和计算机内存如何协同工作,虚拟机相当于一个完整的计算机模型,而这个虚拟机的内存模型就为——Java内存模型

运行在Java虚拟中的线程的线程栈

每一个运行的线程都会有自己的线程栈,线程栈里包括这个线程当前调用的方法的相关信息,每个线程只能访问自己的线程栈,仅自己可见,即使多个线程执行相同的代码,每个线程都会在自己的线程栈中创建本地变量,都是自己的独有版本。

Java虚拟中的堆

所有对象都放在堆中,即使不同的线程同时访问同一个对象,那么只是线程栈指向同一个对象,但是对象的值相关信息,都是在自己的线程栈中。

计算机的硬件处理数据流程

由于CPU的运算速度很高,但是数据的传递速度是远小于运算速度的,所以在CPU附近会有多级缓存,以三级缓存来说(现在都有四级缓存了),其中L1和L2级缓存为线程独享的,L3为线程共享的,CPU在需要数据时,会先从一级缓存中寻找,一般有80%概率能找到,如果找不到就依次像下寻找,速度也会越来越慢,缓存区大小也越来越大,当从主内存中寻找到数据,就会依次向上缓存,然后当CPU修改了某一个值int i= 20,他不会立马更新到主内存中,他会放在以及缓存中,这就涉及到缓存一致协议(MESI),出处:
M 修改 (Modified),数据被修改了,和内存中的数据不一致,数据只存在于本 Cache 中
E 独享、互斥 (Exclusive) 该 Cache line 有效,数据和内存中的数据一致,数据只存在于本 Cache 中
S 共享 (Shared) 该 Cache line 有效,数据和内存中的数据一致,数据存在于很多 Cache 中 Cache
I 无效 (Invalid) 该 Cache line 无效 无监听任务

Core 0在主存中读取int a= 1,然后在依次向上缓存,然后在Cache中标记int a = 1为E,也就是自己独享这个参数,然后这时Core 1去区主存中读取int a = 1,这时核心Core 0 监听到其他线程也去在主存中读取该数据,然后Core 0就会做出反应,把标记int a = 1为S,也就是共享状态,然后Core 1也会缓存a,并且标记为S,假设此时Core 0 需要对a进行加一,首先把a设置为M态,也就是修改态,然后通知其他核心,把自己缓存中的a设置为无效状态,然后Core 0修改a
由于CPU的运算速度很高,且需要等待其他核心的无效确认的过程会比较漫长,所以引入了存储缓存(Store Buffers),核心会把a=2放入到存储缓存中,等到所有无效确认之后,才把a修改后的值一次向下修改,最终把主存中的a值改过来

什么时候把a更新到主存:
然后等到其他线程用到a时,会通知Core 0 ,把a同步到主存中,同步的过程中会把a设置为E独享状态,同步完成后会把Core0和其他线程的a设置为E共享状态,以上都是硬件层面去保证的。

Java并发——Java内存模型_第1张图片

以上无效状态通知或者其他的状态通知,因为要等待无效的成功状态,所以CPU会处于阻塞状态,所以CPU会把逻辑运算完的A值,放入到存储缓存(Store Bufferes),但存储缓存不是无限大的,当他容量满的时候,CPU还是要自己等待无效的ack,而需要给反馈的CPU可能这在做其他的事,不能及时给出反馈,所以为了避免CPU这段时间空闲,加入了无效队列(invalid queue),当来了个无效状态通知,就把他放入到队列中,然后先给出ack,告诉发出无效通知的cpu,数据已经无效,但是真正的无效是等cpu合适的时间去执行。

引入存储缓存和无效队列在并发的时候产生了两个问题:
1.可见性问题,因为无效的消息被放入到了失效队列,让数据不是立马失效,让CPU感知不到数据的变化
2.指令重拍,因为要让CPU一直工作,所以会优化程序的执行顺序,比如读取int a,因为缓存中没有,就得取主存中取,而b的值在告诉缓存·中有,就会优化为先读b,后读取a,这就会在并发的时候造成问题,比如初始化两个值

public class VolatileExample {
	int a=0;
	volatile boolean flag=false;
	
	public void writer(){
		a=1;   //1
		flag=true;//2
	}
	public void reader(){
		if(flag){//3
			int i=a;//4
		}
	}
}

Java控制线程安全

Volatitle

Volatitle通过内存屏障来可见性和指令重拍带来的有序性问题;
读屏障针对无效队列,让CPU立马去读取无效队列,使对应的数据失效
写屏障针对存储缓存,让CPU立马去把所有存储缓存的数据更新到主存中
同时,这两个屏障是的前后都是不允许有指令优化重拍的,也就保证了有序性

CAS

利用CAS来保证原子性,但是只能保证一个变量,不过在JDK1.5之后引入了AtomicReference,可以保证一个对象的所有属性的原子性,且他解决了CAS的ABA问题和智能保证一个变量的原子性问题,同时改变为自适应自旋锁,因为不可能一直自选操作,会影响CPU效率和占用线程,会根据上次自选成功的次数,进行阈值设定

所以CAS和Volatile保证3个特性组成并发包最基础的结构

你可能感兴趣的:(java)