JVM内存模型

JMM

JMM是一套多线程读写共享数据时,对数据的可见性,有序性和原子性的原则;为什么会有内存模型这个概念?因为不同JVM版本实现不同会造成“翻译”的效果不同,不同CPU平台的机器指令有千差万别,无法保证同一份代码并发下的效果一致。所以需要一套统一的规范来约束JVM的翻译过程,保证并发效果一致性。

JMM的实现是在Unsafe类中

public final class Unsafe {
/**
     * Fetches a reference value from a given Java variable, with volatile
     * load semantics. Otherwise identical to {@link #getObject(Object, long)}
     */
// 从主内存中加载变量到工作内存中
public native Object getObjectVolatile(Object o, long offset);

/**
     * Stores a reference value into a given Java variable, with
     * volatile store semantics. Otherwise identical to {@link #putObject(Object, long, Object)}
     */
// 实现save原语
public native void putObjectVolatile(Object o, long offset, Object x);
}

很多原子操作都是建立在以上操作的基础上实现的:

/**
 * Atomically exchanges the given value with the current value of
 * a field or array element within the given object <code>o</code>
 * at the given <code>offset</code>.
 *
 * @param o object/array to update the field/element in
 * @param offset field/element offset
 * @param newValue new value
 * @return the previous value
 * @since 1.8
 */
public final int getAndSetInt(Object o, long offset, int newValue) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, newValue));
    return v;
}

在这里插入图片描述

原子性

什么是原子性

原子性指一系列的操作,要么全部执行成功,要么全部不执行,不会出现执行一半的情况,是不可分的。

原子性怎么实现

  • 使用synchronized或Lock加锁实现,保证任一时刻只有一个线程访问该代码块
  • 使用原子操作

Java中的原子操作有哪些

  • 除long和double之外的基本类型的赋值操作(64位值,当成两次32位的进行操作)
  • 所有引用reference的赋值操作
  • java.concurrent.Atomic.*包中所有类的原子操作

创建对象的过程是否是原子操作(常应用于双重检查+volatile创建单例场景)

创建对象实际上有3个步骤,并不是原子性的
1.创建一个空对象
2.调用构造方法
3.创建好的实例赋值给引用

可见性

什么是可见性问题

可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值。

为什么会有可见性问题

对于单线程程序来说,可见性是不存在的,因为我们在任何一个操作中修改了某个变量的值,后续的操作中都能读取这个变量值,并且是修改过的新值。
对于多线程程序而言。由于线程对共享变量的操作都是线程拷贝到各自的工作内存进行操作后才写回到主内存中的,这就可能存在一个线程A修改了共享变量x的值,还未写回主内存时,另外一个线程B又对主内存中同一个共享变量x进行操作,但此时A线程工作内存中共享变量x对线程B来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题

如何解决可见性问题

  • 解决方法1:加volatile关键字保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值立即更新到主存中,当其他线程使用volatile修饰的变量前,会立即从主内存中刷新数据,保证读取的数据都是最新的;
  • 解决方法2:使用synchronized和Lock保证可见性。因为它们可以保证任一时刻只有一个线程能访问共享资源,并在其释放锁之前将修改的变量刷新到内存中

有序性(重排序)

什么是重排序

在线程内部的两行代码的实际执行顺序和代码在Java文件中的逻辑顺序不一致,代码指令并不是严格按照代码语句顺序执行的,他们的顺序被改变了,这就是重排序。

重排序的意义

JVM能根据处理器特性(CPU多级缓存系统、多核处理器等)适当的对机器指令进行重排序,使机器指令能更符合CPU的执行特性,最大限度的发挥机器性能。

重排序的3种情况

编译器优化( JVM,JIT编辑器等): 编译器在不改变单线程程序语义放入前提下,可以重新安排语句的执行顺序
指令级并行的重排序:现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
内存系统的重排序: 由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

volatile

什么是volatile

  • volatile是一种同步机制,比synchronized或者Lock相关类更轻量级,因为使用volacile并不会发生上下文切换等开销很大的行为

此处的上下文切换开销指的是操作系统层级线程切换的开销,在有锁的场景下会涉及到线程的调度和资源抢占等操作

  • volatile是无锁的,并且只能修饰单个属性

什么时候适合用volatile

一个共享变量始终只被一个线程赋值,没有其他操作
作为刷新的触发器,引用刷新之后使修改内容对其他线程可见(如CopyOnRightArrayList底层动态数组通过volatile修饰,保证修改完成后通过引用变化触发volatile刷新,使其他线程可见)

volatile的作用

  • 可见性保障:修改一个volatile修饰变量之后,会立即将修改同步到主内存,使用一个volatile修饰的变量之前,会立即从主内存中刷新数据。保证读取的数据都是最新的,之前的修改都是可见的。
  • 有序性保障(禁止指令重排序优化):有volatile修饰的变量,赋值后多了一个“内存屏障“( 指令重排序时不能把后面的指令重排序到内存屏障之前的位置)
  • volatile的性能
    volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行

happens-before规则

什么是happens-before规则

前一个操作的结果可以被后续的操作获取。

  • 程序的顺序性规则:在一个线程内一段代码的执行结果是有序的。虽然还会指令重排,但是随便它怎么排,结果是按照我们代码的顺序生成的不会变!
  • volatile规则: 就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。
  • 传递性规则:happens-before原则具有传递性,即A happens-before B , B happens-before C,那么A happens-before C。
  • 管程锁定规则:无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果!(管程是一种通用的同步原语,synchronized就是管程的实现)
  • 线程启动规则:在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。
  • 线程终止规则: 在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。
  • 线程中断规则: 对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。
  • 对象终结规则:一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before它的finalize()方法。
  • 线程中断规则: 对线程interrupt()方法的调用先行发生于被中断线程代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。
  • 对象终结规则:一个对象的初始化的完成,也就是构造函数执行的结束一定 happens-before它的finalize()方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值