美文网首页右耳菌-邓小白的Java架构师的修炼之路
Java线程安全 - 内存模型和相关的一些知识

Java线程安全 - 内存模型和相关的一些知识

作者: 右耳菌 | 来源:发表于2022-06-15 15:13 被阅读0次

多线程的问题

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


volatile 关键字

  • 可见性问题
    让一个线程对共享变量的修改,能够及时的被其他线程看到。
  • Java内存模型规定:
    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 变量相关的指令不做重排序
    即JVM在进行编译的时候,正常情况下,会在JVM规定的规范内,对指令进行重排序,以提高运行效率(性能),而对于volatile修饰的内容,则不会进行重新排序。


Shared Variables 共享变量定义

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

定义在: https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4.1


线程间操作的定义

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

  • 操作间操作有:
  • read操作(一般读,即非volatile读)
  • write操作(一船写,即非volatile写)
  • volatile read
  • volatile write
  • Lock.(锁monitor)、Unlock
  • 线程的第一个和最后一个操作
  • 外部操作

对于同步的规则定义

  • 对volatile变量v的写入,与所有其他线程后续对v的读同步
  • 对于监视器m的解锁与所有后续操作对于m的加锁同步
  • 对于每个属性写入默认值(0, false,null)与每个线程对其进行的操作同步
  • 启动线程的操作与线程中的第一个操作同步
  • 线程T2的最后操作与线程T1发现线程T2已经结束同步。( isAlive ,join可以判断线程是否终结)
  • 如果线程T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步通过抛出InterruptedException异常,或者调用Thread.interrupted或Thread.isInterrupted

Happens-before先行发生原则

happens-before关系用于描述两个有冲突的动作之间的顺序,如果一个action happends 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.

当程序包含两个没有被happens-before关系排序的冲突访问时,就称存在数据竞争
遵守了这个原则,也就意味着有些代码不能进行重排序,有些数据不能缓存!


final 在JVM中的处理

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

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

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

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


Word Teading 字节处理

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

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

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


double 和 long 的特殊处理

由于《Java语言规范》的原因,对非volatile的double、long的单次写操作是分两次来进行的,每次操作其中32位,这可能导致第一次写入后,读取的值是脏数据,第二次写完成后,才能读到正确值。


  • 读写volatile修饰的long、double是原子性的。
  • 商业JVM不会存在这个问题,虽然规范没要求实现原子性,但是考虑到实际应用,大部分都实现了原子性。
    -《Java语言规范》中说道:建议程序员将共享的64位值(long、double)用volatile修饰或正确同步其程序以避免可能的复杂的情况。

如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~

相关文章

  • Android下多线程的实现

    Android下多线程相关 线程安全相关问题参考:java内存模型与线程 android下与多线程有关的主要有以下...

  • 并发编程有关知识点(五)

    Java 内存模型 java线程安全总结 深入理解java内存模型系列文章 线程状态: 一张图让你看懂JAVA线程...

  • Java线程安全 - 内存模型和相关的一些知识

    多线程的问题 1、所见非所得2、无法肉眼去检测程序的准确性3、不同的运行平台有不同的表现4、错误很难重现 vola...

  • 高并发Java

    高并发Java(1):前言 高并发Java(2):多线程基础 高并发Java(3):Java内存模型和线程安全 高...

  • java并发编程(四)

    java多线程编程(四) 引言: 内存模型的基础----内存模型相关的基本概念 java内存模型中的顺序一致性--...

  • 高效并发

    从JVM的角度看一下Java与线程,内存模型,线程安全以及JVM对于锁的优化 硬件内存模型与JVM内存模型 硬件的...

  • Java 线程安全 与 锁

    Java 线程安全 与 锁 多线程内存模型 线程私有栈内存每个线程 私有的内存区域 进程公有堆内存同一个进程 共有...

  • JAVA并发编程之JAVA内存模型(JMM)

    JAVA并发编程之JAVA内存模型(JMM) 相关概念 进程和线程的区别与联系 进程是资源分配的最小单位,线程是程...

  • java内存模型(JMM)解刨

    JMM简介 Java的内存模型JMM(Java MemoryModel)JMM主要是为了规定了线程和内存之间的一些...

  • 初识Java虚拟机

    1:Java虚拟机内存模型主要分为5大区域,其中共有区域(线程非安全)为:Java堆、方法区,线程私有(线程安全)...

网友评论

    本文标题:Java线程安全 - 内存模型和相关的一些知识

    本文链接:https://www.haomeiwen.com/subject/nyrxvrtx.html