Java内存模型(JMM)

并发编程模型中的俩个关键问题

在并发编程中,需要处理俩个关键问题:线程之间如何通信及线程之间如何同步。通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有俩种:共享内存和消息传递。说起通信不得不说Java内存模型,Java线程之间的通信可由JMM来解释。

Java内存模型(JMM)

Java线程之间的通信可以又Java内存模型来解释。Java内存模型决定了一个线程对共享变量的写入何时对另一个线程可见(即一个线程调用store和write操作另一线程调用read、load操作的时候可见)。从抽象觉得来看,JMM定义了线程主内存工作内存之间的关系,如图下图所示:
图1.1

Java内存模型规定所有的变量都存储在主内存中,每个线程都有自己的工作内存,线程的工作内存中保存了该线程使用到的变量的副本,线程对变量的所有操作(读,写)都必须在工作内存中进行,而不能直接访问主内存中的变量。不同的线程之间无法直接访问对方工作内存中的变量,线程间变量的正确传递都需要通过主内存来完成。主内存和工作内存是如何交互的呢?Java内存模型定义了8中操作来实现交互。

内存间的交互操作

主内存与工作内存之间的交互协议,即一个变量如何从主内存拷贝到工作内存。如何从工作内存同步到主内存中的实现细节。java内存模型定义了8种操作来完成。这8种操作每一种都是原子操作。8种操作如下:

  • lock(锁定):作用于主内存,它把一个变量标记为一条线程独占状态;
  • unlock(解锁):作用于主内存,它将一个处于锁定状态的变量释放出来,释放后的变量才能够被其他线程锁定;
  • read(读取):作用于主内存,它把变量值从主内存传送到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存,它把read操作的值放入工作内存中的变量副本中;
  • use(使用):作用于工作内存,它把工作内存中的值传递给执行引擎,每当虚拟机遇到一个需要使用这个变量的指令时候,将会执行这个动作;
  • assign(赋值):作用于工作内存,它把从执行引擎获取的值赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的指令时候,执行该操作;
  • store(存储):作用于工作内存,它把工作内存中的一个变量传送给主内存中,以备随后的write操作使用;
  • write(写入):作用于主内存,它把store传送值放到主内存中的变量中。

    如果要把一个变量从主内存复制到工作内存,那就要按顺序的执行read和load操作,如果要把变量从工作内存同步到主内存,那就要按顺序的执行store和write操作。注意,Java内存模型只要求上述俩个操作必须按顺序执行,而没有保证必须是连续执行。也就是说read和load、store和write之间是可插入其他指令的,如对主内存中的变量a、b进行访问时,一种可能出现的顺序是read a、read b、load b、load a。除此之外,Java内存模型还规定了在执行上述8中基本操作时必须满足如下规则:

  • 不允许read和load、store和write操作之一单独出现,以上两个操作必须按顺序执行,但没有保证必须连续执行,也就是说,read与load之间、store与write之间是可插入其他指令的。

  • 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。(允许合并多次赋值操作,最后一次的赋值操作必须同步回主内存)
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
  • 一个新的变量只能从主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一个时刻只允许一条线程对其执行lock操作,但lock操作可以被同一个条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量实现没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定的变量。
  • 对一个变量执行unlock操作之前,必须先把此变量同步回主内存(执行store和write操作)。

Java内存模型来解释线程之间的通信。

线程通信的抽象内存模型图如下所示
线程通信的抽象内存模型图1.2

通信的俩个步骤

在上图中的线程A和线程B需要正确通信的话,必须经历如下俩个过程。
1) 线程A把本地共享的副本变量刷新到主内存中去。
2) 线程B到主内存中去读取线程A更新过的共享变量。

说明这俩个步骤

在本地内存A和本地内存B都存储了共享变量X的副本,假设初始时,这三个内存中X的值都是0。线程A在执行是,把更新后的X的值(假设此时为1)临时放在自己的本地内存中。当线程A和线程B需要正确通信时,线程A首先会把自己本地内存中修改后的X的值刷新回主内存,此时主内存中X的值为1。随后线程B到主内存中去读取线程A更新的X的值,此时线程B的本地内存X的值也为1。只有这样才能保证线程间的正确通信。

总结

Java内存模型是用来分析线程之间的通信问题,不能阻止通信问题的发生。JMM模型定义在满足通信的俩个步骤前提下,一个线程的写对另一线程是可见的。在其他情况下,都有可能会存在不可见问题。
JMM又定义了volatile和synchronize俩个关键字来满足通信的俩个步骤,是用来解决线程通信问题的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值