Java内存模型详解

JVM运行时数据区

在这里插入图片描述

Java内存模型 VS JVM运行时数据区

在这里插入图片描述
Java语言规范:描述java语言特性
Java虚拟机规范:描述java虚拟机模型
Java内存模型是由,java语言规范提出
JVM运行时数据区(堆,方法区。。。)来自ava虚拟机规范

初看Java内存模型

一次执行单个语句或表达式,即通过单个线程来执行。Java虚拟机可以同时支持多个线程执行,若未正确同步,线程的行为可能会出现混淆和违反直觉。
多线程的语义:当多个线程修改了共享内存中的值时,应该读取到哪个值得规则。由于这部分规范类似于不同硬件体系结构的内存模型,因此这些语义成为Java编程语言内存模型。
这些语义没有规定如何执行多线程程序。相反,它们描述了允许多线程程序的合法行为。

多线程中的问题

1、所见非所得
2、无法肉眼去检测程序的准确性
3、不同的运行平台有不同的表现
4、错误很难重现
在这里插入图片描述

CPU指令重排序

Java编程语言的语义允许Java编译器微处理器进行执行优化,这些优化导致了与其交互的代码不再同步,从而导致看似矛盾的行为。

JIT编译器(Just In Time Compiler)

脚本语言编译语言的区别:
解释执行: 即脚本语言,由语言的解释器将其一条条翻译成机器可识别的指令。
编译执行:将我们编写的程序,直接编译成机器可以识别的指令码。即:批量编译
在这里插入图片描述
效果:缓存到方法区,直接从方法区获取代码,不用重新编译,提高运行速度

Volatile

可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到。
根据JMM中规定的happen before和同步原则:
对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作。
对volatile变量v的写入,与所有其他线程后续对v的读同步。
要满足这些条件,所以volatile关键字就有这些功能:
1、禁止缓存;
volatile变量的访问控制符,会加个ACC_VOLATILE
在这里插入图片描述
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.5
2、对volatile变量相关的指令不做重排序;

Shared Variables定义

可以在线程之间共享的内润称为共享内存或堆内存
所有实例字段静态字段数组元素都存储在堆内存中,这些字段和数组都是标题中提到的共享变量
冲突:如果至少有一个访问时写操作,那么对同一个变量的两次访问是冲突的。
这些能被多个线程访问的共享变量是内存模型规范的对象。

线程间操作的定义

1、线程间操作:一个程序执行的操作可被其他线程感知或被其他线程直接影响
2、Java内存模型只描述线程间操作,不描述线程内操作,线程内操作按照线程内语义执行。
线程间操作:

  • read操作(一般读,即非volatile读)
  • write操作(一般写,即非volatile写)
  • volatile read
  • volatile write
  • Lock(锁monitor)、Unlock
  • 线程的第一个和最后一个操作
  • 外部操作
    所有线程间操作,都存在可见性问题,JMM需要对其进行规范。

对于同步的规则定义

  • 对 volatile变量v的写入,与所有其他线程后续对v的读同步(可见)

  • 对于监视器m的解锁,与所有后续操作对于m的加锁同步(可见)。
    在这里插入图片描述

  • 对于每个属性写入的默认值(0,null,false)与每个线程对其进行的操作同步。

  • 启动线程的操作与线程中的第一个操作同步
    在这里插入图片描述
    state状态,不会被缓存,
    t2线程的状态变为Runnable,t2线程的第一个操作才会执行。

  • T2线程的最后操作,和T1线程发现T2线程已经结束同步。(isAlive,join可以判断线程是否中止)
    在这里插入图片描述

  • 如果线程T1中断了线程T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步。
    通过抛出InterruptedException异常,或者调用Thread.interrupted或者Thread.isInterrupted。

interrupt()方法不会改变线程状态,让线程抛异常。
inInterrupted字段不会被缓存。

Happens-before先行发生原则

happends-before关系用于描述两个有冲突的动作之间的顺序,如果一个action happens before另一个action,则第一个操作被第二个操作可见,JVM需要实现如下happens-before规则:

  • 某个线程中的每个动作都happens-before该线程中,该动作后面的动作。
  • 某个管程上的unlock动作happens-before同一个管程上后续的lock动作。
  • 对某个volatile字段的写操作happens-before每个后续对该volatile字段的读操作。
  • 在某个线程对象上调用Start()方法happens-before被启动线程中的任意动作。
  • 如果在线程t1中成功执行了t2.join(),则t2中的所有操作对t1可见。
  • 如果某个动作a happens-before动作b,且b happens-before动作c ,则a happens-before c。

final在JMM中的处理

final在该对象的构造函数中设置对象的字段,当线程看到该对象时,将始终看到该对象的final字段的正确构造版本。
伪代码示例:f = new FinalDemo(); 读取到的 f.x 一定最新,x为final字段。

如果在构造函数中设置字段后发生读取,则会看到该final字段分配的值,否则它将看到默认值;
伪代码示例:public finalDemo(){x=1; y=x;} y会等一1;

读取该共享对象的final成员变量之前,先要读取共享对象。
伪代码示例:r = new ReferenceObj(); k = r.f; 这两个操作不能重排序。

通常被static final修饰的字段,不能被修改。然而System.in、System.out、System.err被static final修饰,却可以修改,遗留问题,必须允许通过set方法改变,我们讲这些字段成为写保护,以区别于普通final字段。

Word Tearing字节处理

有些处理器(尤其是早期的Alphas处理器)没有提供写单个字节的功能。在这样的处理器上更新byte数组,若只是简单的读取整个内容,更新对应的字节,然后将整个内容再协会内存,将是不合法的。

这个问题有时候被称为“字分裂(word tearing)”,更新单个字节有难度的处理器,就需要寻求其他方式来解决问题。

因此,编程人员需要注意,尽量不要对byte[]中的元素,进行重新复制,更不要在多线程程序中这样做。

double和long的特殊处理

  • 由于《Java语言规范》的原因,对非volatile的double、long的单次写操作时分两次来进行的,每次操作其中的32位,这可能导致第一次写入后,读取的值时脏数据,第二次写完成后,才能督导正确值。
    在这里插入图片描述
  • 读写volatile修饰的long、double是原子性的。
  • 商业JVM不会存在这个问题,虽然规范没要求实现原子性,但是考虑到实际应用,大部分都实现了原子性。
  • 《Java语言规范》中说到:建议程序员将共享的64位值(long、double)用volatile修饰或正确同步其程序以避免可能的复杂的情况。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值