java并发编程系列 ---(二)java 内存模型解析(JMM)

文章目录

  • java 内存模型
    • 概述
        • 工作内存数据和主内存数据
    • 硬件内存架构与Java内存模型
      • Java线程与硬件处理器
    • JMM(java内存模型)存在的必要性
    • Java内存模型三大特性
        • 原子性
        • 可见性
        • 有序性
    • JMM提供的解决方案
        • 举例说明:
        • 方法一:使用volatile关键字
        • 方法二: synchronized
        • 方法三:Locks
        • 另一个实例

java 内存模型

本篇文章主要参考来自:
理解Java内存区域与Java内存模型
Java Memory Model in 10 minutes

概述

Java内存模型(即Java Memory Model,简称JMM)本身是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存,用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,工作内存中存储着主内存中的变量副本拷贝,因为工作内存是每个线程的私有数据区域,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。

工作内存数据和主内存数据

那么哪些数据存储在工作内存,哪些数据存储在主内存呢?

  • 主内存
    主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是成员方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发现线程安全问题。存储的数据类型也就是堆存储和方法区存储的数据:new的对象,成员变量,静态和常量。

  • 工作内存
    注意由于工作内存是每个线程的私有数据,线程间无法相互访问工作内存,因此存储在工作内存的数据不存在线程安全问题。工作内存相当于栈存储数据类型,包括:本地变量,对象的引用。

java并发编程系列 ---(二)java 内存模型解析(JMM)_第1张图片

硬件内存架构与Java内存模型

java并发编程系列 ---(二)java 内存模型解析(JMM)_第2张图片

Java线程与硬件处理器

这里其实就是我们调用多个线程来处理多个任务,而每个线程不一定会被分配到一个cpu,而是统一到系统的线程调度器,具体由几个CPU来执行由系统说了算,这个我们无需担心,我们只需要关注调用了多个任务调用多个线程就可以了。

这里需要了解一个术语,内核线程(Kernel-Level Thread,KLT),它是由操作系统内核(Kernel)支持的线程,这种线程是由操作系统内核来完成线程切换,内核通过操作调度器进而对线程执行调度,并将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这也就是操作系统可以同时处理多任务的原因。由于我们编写的多线程程序属于语言层面的,程序一般不会直接去调用内核线程,取而代之的是一种轻量级的进程(Light Weight Process),也是通常意义上的线程,由于每个轻量级进程都会映射到一个内核线程,因此我们可以通过轻量级进程调用内核线程,进而由操作系统内核将任务映射到各个处理器,这种轻量级进程与内核线程间1对1的关系就称为一对一的线程模型。

JMM(java内存模型)存在的必要性

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),用于存储线程私有的数据,线程与主内存中的变量操作必须通过工作内存间接完成,如果存在两个线程同时对一个主内存中的实例对象的变量进行操作就有可能诱发线程安全问题。

如下图,主内存中存在一个共享变量x,现在有A和B两条线程分别对该变量x=1进行操作,A,B线程各自的工作内存中存在共享变量副本x。假设现在A线程想要修改x的值为2,而B线程却想要读取x的值,那么B线程读取到的值是A线程更新后的值2还是更新前的值1呢?答案是,不确定,即B线程有可能读取到A线程更新前的值1,也有可能读取到A线程更新后的值2,这是因为工作内存是每个线程私有的数据区域,而线程A操作变量x时,首先是将变量从主内存拷贝到A线程的工作内存中,然后对变量进行操作,操作完成后再将变量x写回主内,而对于B线程的也是类似的,这样就有可能造成主内存与工作内存间数据存在一致性问题,这也就是所谓的线程安全问题。
在这里插入图片描述
为了解决类似上述的问题,JVM定义了一组规则,通过这组规则来决定一个线程对共享变量的写入何时对另一个线程可见,这组规则也称为Java内存模型(即JMM),JMM是围绕着程序三个特性的原子性、有序性、可见性展开的,下面我们看看这三个特性。

Java内存模型三大特性

原子性

原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。比如对于一个静态变量int x,两条线程同时对他赋值,线程A赋值为1,而线程B赋值为2,不管线程如何运行,最终x的值要么是1,要么是2,线程A和线程B间的操作是没有干扰的,这就是原子性操作,不可被中断的特点。如果不能保证原子性,则一个变量可能就会被赋值成意想不到的结果。

可见性

可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值。对于串行程序来说,任何一个操作中修改了某个变量的值,后续的操作中都能读取这个变量修改过的值。但在多线程环境中可就不一定了,一个线程A修改了共享变量x的值,还未写回主内存时,另外一个线程B又对主内存中同一个共享变量x进行操作,但此时A线程工作内存中共享变量x对线程B来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题。也就是说我们买票的时候,只剩一张票,线程1买完了,而线程2不知道,他又去买,这就造成一张票卖了两次的问题。

有序性

有序性是指对于单线程的执行代码,我们总是认为代码的执行是按顺序依次执行的,但对于多线程环境,则可能出现乱序现象,因为程序编译成机器码指令后可能会出现指令重排现象,重排后的指令与原指令的顺序未必一致。
比如

线程 1             线程 2
1: x2 = a ;      3: x1 = b ;
2: b = 1;         4: a = 2 ;

在这里CPU可能会对多线程来的指令进行重拍,1234打乱顺序,这时就会造成操作的数据出现问题。这也就说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的。所以有序性也是需要我们来解决的问题。

JMM提供的解决方案

举例说明:

java并发编程系列 ---(二)java 内存模型解析(JMM)_第3张图片
当我们没有任何保护时,如果我们使用多线程来执行这段代码,两个方法在不同的线程执行,当然先执行写线程,当写线程修改完x=1未来得及放回共享内存时,读线程就已经读x=0的值了,这时并不能读到修改后的x的值1。所以出现错误。
如何解决呢?

方法一:使用volatile关键字

java并发编程系列 ---(二)java 内存模型解析(JMM)_第4张图片
java并发编程系列 ---(二)java 内存模型解析(JMM)_第5张图片
因为java 存在happens-before原则,因为x是读线程操作,也就是说当我们操作x时,只有写线程在操作,这时他会操作完成所有abc变量的修改。而读线程会等到操作其他线程操作x=1后再操作这个变量,这时操作完x再操作abc,所以这不会出现同步问题。
使用volatile关键字后,这就是说线程能够自动发现 volatile 变量的最新值,但是volatile不能保证一次就只有一个线程能够使用该共享数据。所以一些情况下volitale还是存在同步安全问题。

方法二: synchronized

锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。
使用synchronized关键字可以保证x被一个线程修改完之后,才被另一个线程修改。也就是说一次只能被一个线程操作。
java并发编程系列 ---(二)java 内存模型解析(JMM)_第6张图片
这样操作也可以解决问题。注意:synchronized关键字需要操作相同的对象。操作不同的对象同步将无效。
java并发编程系列 ---(二)java 内存模型解析(JMM)_第7张图片
所以我们可以把所有变量都放入synchronized块中来保证同步。

方法三:Locks

java并发编程系列 ---(二)java 内存模型解析(JMM)_第8张图片
把要同步的快加入到锁中

另外两种方式:

  • Concurrent collection
  • Thread operation(start, join)

另一个实例

java并发编程系列 ---(二)java 内存模型解析(JMM)_第9张图片

你可能感兴趣的:(java并发编程)