一、什么是Java内存模型

Java内存模型(JMM)是根据英文Java Memory Model翻译过来的。JMM不像JVM内存结构一样真实存在,它只是一个抽象的概念。

Java内存模型的相关知识在JSR-133:Java Memory Model and Thread Specification 中描述。JMM是和多线程相关的,它描述了一组规范,这个规范定义了一个线程对共享变量的写入时对另一个线程时可见的。

Java内存模型就是一种符合内存模型规范,屏蔽了各种硬件和操作系统的访问差异,保证Java程序在各种平台下对内存的访问都能得到一致性结果的机制和规范。目的是解决由于多线程通过共享内存进行通信时,存在的原子性、可见性(缓存一致性)以及有序性问题。

原子性:
线程时CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。所以在多线程场景下,就会发生原子性问题。因为线程在执行一个读改写操作时,在执行完读改之后,时间片用完,就会被要求放弃CPU并等待重新调度。这种情况下,读改写就不是一个原子操作。即存在原子性问题。

可见性
在多核CPU,多线程场景中,每个核都至少有一个L1缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行,则每个核心都会在各自的cache中保留一份共享内存的缓冲。由于多核是可以并行的,可能会出现多个线程同时写各自缓存的情况,而各自的cache之间的数据就有可能不同。

在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致。

有序性
除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码继续乱序执行,比如load->add->save有可能被优化成load->save->add。这就是有序性问题。

CPU时间片机制导致的原子性问题、多CPU多级缓存导致的一致性问题、以及处理器优化和指令重排导致的有序性问题等,都是硬件的不断升级导致的。

为了保证并发编程中可以满足原子性、可见性、有序性。有一个重要概念:内存模型。

为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器、缓存、并发、编译器都有关。它解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的原子性、可见性和有序性。

Java语言为了屏蔽掉底层的差异,定义了一套属于Java语言的内存模型规范,即Java内存模型。

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存。不同的线程之间也无法直接访问其它线程的工作内存中的变量,线程间变量的传递均需要自己的工作内存和主内存之间进行数据同步来完成。JMM就作用于工作内存和主存之间的数据同步过程,它规定了如何以及什么时候做数据同步。
JVM之浅谈内存模型【七】

二、Java内存模型的实现

在Java中提供了一系列和并发处理相关的关键字,比如volatile、synchronized、final、concurrent包等。其实这些就是Java内存模型封装了底层的实现后提供给程序员使用的关键字。

在开发多线程的代码的时候,我们可以直接使用synchronized等关键字来控制并发,从来不需要关心底层的编译器优化、缓存一致性等问题。所以,Java内存模型除了定义了一套规范,还提供了一系列原语,封装了底层实现后,供开发者直接使用。

在Java中,并发编程要解决原子性、可见性、有序性的问题,分别使用以下方式来保证。

原子性
在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit,这两个字节码在java中对应的关键字就是synchronized。在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。

可见性
Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。

Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在修改后可以立即同步到主内存,被其修饰的变量在每次使用之前都从主内存刷新。因此可以使用volatile来保证多线程操作时变量的可见性。

除了volatile,Java中的synchronized和final两个关键字也可以实现可见性。只不过实现方式不同。

有序性
在Java中,可以使用synchronized和volatile来保证多线程之间操作的有序性。实现方式有以下区别:

volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

可以看出,synchronized是万能的,可以同时满足原子性、可见性、有序性,以前使用synchronized会很影响性能,但是随着JVM的发展,synchronized采用了很多锁优化技术,其实性能已经很好了。

相关文章:

  • 2021-04-20
  • 2021-11-29
  • 2021-10-29
  • 2021-04-18
  • 2021-10-03
  • 2021-08-26
  • 2021-08-22
猜你喜欢
  • 2021-05-25
  • 2021-12-18
  • 2021-07-31
相关资源
相似解决方案