zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java内存模型

JAVA内存 模型
2023-09-14 08:59:00 时间

Java内存模型

概念:java内存模型定义了多线程之间共享变量的可见性以及如何在需要的时候对共享变量进行同步。

线程之间的通信

两种通信机制:

  • 共享内存

    写读内存中公共状态来通信,通过共享对象进行通信。

  • 消息传递

    明确的发送消息来进行通信,java中就是wait() notify().

java内存模型

  1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。

  2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

  1. 线程A本地内存中的x值刷新到主内存中。

  2. 线程B从主内存中获取X的值并更新到本地内存中。

一个本地变量如果是原始类型,那么它会被完全存储到栈区。
一个本地变量也有可能是一个对象的引用,这种情况下,这个本地引用会被存储到栈中,但是对象本身仍然存储在堆区。

对于一个对象的成员方法,这些方法中包含本地变量,仍需要存储在栈区,即使它们所属的对象在堆区。
对于一个对象的成员变量,不管它是原始类型还是包装类型,都会被存储到堆区。

Static类型的变量以及类本身相关信息都会随着类本身存储在堆区。

堆中的对象可以被多线程共享。如果一个线程获得一个对象的应用,它便可访问这个对象的成员变量。如果两个线程同时调用了同一个对象的同一个方法,那么这两个线程便可同时访问这个对象的成员变量,但是对于本地变量,每个线程都会拷贝一份到自己的线程栈中。

共享对象的可见性

本地缓存没有同步到主缓存,就会导致其他线程无法看到修改后的内容。

要解决共享对象的可见性,可以使用volatile,volatile可以保证变量直接从内存读写。

竞争现象

主内存在两个不同的工作内存中副本进行变动,比如主存变量值为1,A工作内存+1,B工作内存+1,最终主存应该是3,但是并行处理中的A和B工作内存flush到主存,结果为2.

要解决这种问题,synchronized 保证同一时刻只有一个线程来执行,并保证变量从主从读,退出代码块,会flush到主存。

内存屏障(Memory Barrier )

  1. 保证特定操作的执行顺序。
  2. 影响某些数据(或则是某条指令的执行结果)的内存可见性。

Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。

如果一个变量是volatile修饰的,JMM会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。这意味着,如果写入一个volatile变量,就可以保证:

  1. 一个线程写入变量a后,任何线程访问该变量都会拿到最新值。
  2. 在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。

happens-before

如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。

  1. 程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。
  2. 监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。
  3. volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。
  4. 传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。

两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。