Java内存模型(JMM)

Posted hequnwang10

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java内存模型(JMM)相关的知识,希望对你有一定的参考价值。

Java内存模型(Java Memory Model,JMM)JMM主要是为了规定了线程和内存之间的一些关系
根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。
每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。
**Java内存模型(JMM)**是一种抽象的概念,并不真实存在,定义了Java程序在各种平台下对内存访问的机制及规范。线程是程序运行的载体。

内存模型主要是影响线程共享的内存可见性问题,Java线程之间的通信由Java内存模型【JMM】控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。

从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:

  • 线程之间的共享变量存储在主内存(main memory)中
  • 每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。

主内存对应的是Java堆中的对象实例部分,而工作内存对应的则是栈中的部分区域

主内存:
主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中。
由于是共享数据区域,多条线程对同一个变量进行访问可能会发生线程安全问题。

工作内存:
主要存储当前方法的所有本地变量信息【工作内存中存储着主内存中的变量副本拷贝】。

每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,就算是两个线程执行的是同一段代码,它们也会各自在自己的工作内存中创建属于当前线程的本地变量,当然也包括了字节码行号指示器、相关Native方法的信息。

  • 由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。
  • 在主内存中的实例对象可以被多线程共享,倘若两个线程同时调用了同一个对象的同一个方法,那么两条线程会将要操作的数据拷贝一份到自己的工作内存中,执行完成操作后才刷新到主内存。

并发编程会带来什么问题

并发编程会带来原子性问题、可见性问题、有序性问题

  • 原子性,指的是在一个操作中CPU 不可以在中途暂停然后再调度,要么不执行,要么就执行完成。
  • 可见性,指的是多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改后的值。
  • 有序性,程序执行的顺序按照代码的先后顺序执行。对于单线程的执行代码,我们总是认为代码的执行是按顺序依次执行的,但对于多线程环境,则可能出现乱序现象【指令重排导致】,重排后的指令与原指令的顺序未必一致。

线程之间的通信机制可以分为两种,分别是

  1. 共享内存
  2. 消息传递

Java的并发通信采用的是共享内存的方式。

数据同步的八大原子操作

  1. lock(锁定):把一个变量标记为一条线程独占状态
  2. unlock(解锁):把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  3. read(读取):把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  4. load(载入):把read操作从主内存中得到的变量值放入工作内存的变量副本中
  5. use(使用):把工作内存中的一个变量值传递给执行引擎
  6. assign(赋值):把一个从执行引擎接收到的值赋给工作内存的变量
  7. store(存储):把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
  8. write(写入):把store操作从工作内存中的一个变量的值传送到主内存的变量中

JMM如何解决原子性、可见性、有序性问题

1、原子性问题

除了JVM自身提供的对基本数据类型读写操作的原子性外,可以通过synchronizedLock实现原子性。【synchronized和Lock能够保证任一时刻只有一个线程访问该代码块】

2、可见性问题

volatile关键字可以保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值立即被其他的线程看到,即修改的值立即更新到主存中,当其他线程需要读取时,它会去内存中读取新值。
synchronizedLock也可以保证可见性。
因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中。

3、有序性问题

可以通过synchronizedLock来保证有序性。

synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。

并发编程涉及的一些重要原则

as-if-serial语义
  定义:不管怎么重排序【编译器和处理器为了提高并行度】,(单线程)程序的执行结果不能被改变。

为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序, 因为这种重排序会改变执行结果。

如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。

happens-before原则

  1. 程序顺序原则:在一个线程内必须保证语义串行性,也就是说按照代码顺序执行。

  2. 锁规则:解锁(unlock)操作必然发生在后续的同一个锁的加锁(lock)之前,即若对于一个锁解锁后,再加锁,那么加锁的动作必须在解锁动作之后(同一个锁)。

  3. volatile规则:volatile的可见性保证:变量的写,先发生于读。
    简单的理解就是,volatile变量在每次被线程访问时,都强迫从主内存中读该变量的值,而当该变量发生变化时,又会强迫将最新的值刷新到主内存,任何时刻不同的线程总是能够看到该变量的最新值。

  4. 线程启动规则:线程的start()方法先于它的每一个动作,即如果线程A在执行线程B的start()方法之前修改了共享变量的值,那么当线程B执行start()方法时,线程A对共享变量的修改对线程B可见。

  5. 传递性:A先于B ,B先于C 那么A必然先于C。

  6. 线程终止规则:场景:主线程A执行时,B线程调用Thread.join()方法。
    假设在线程B终止之前修改了共享变量,线程A从线程B的join方法成功返回后,线程B对共享变量的修改将对线程A可见。

  7. 线程中断规则:对线程interrupt()方法的调用要比代码检测中断事件先发生,可以通过Thread.interrupted()方法检测线程是否中断。

  8. 对象终结规则:对象的构造函数执行,结束先于finalize()【当垃圾回收器将要回收对象时执行】方法。PS:不推荐使用finalize()方法

以上是关于Java内存模型(JMM)的主要内容,如果未能解决你的问题,请参考以下文章

JMM内存模型JVM内存模型

JVM 内存模型

多线程&高并发深入浅出JMM-Java线程内存模型

java内存模型

java内存模型

关于JAVA中的JMM内存模型