Java并发编程的艺术-synchronized和volatile底层实现原理

Java是并发编程语言
  • 前提:Java代码在编译(javac)后会变成Java字节码字节码(.calss)被类加载器加载到JVM里,JVM执行字节码,最终需要转化为汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令
  • Java编程语言允许线程访问共享变量:为了确保共享变量能被准确一直的更新,线程应该确保通过排它锁单独获得这个变量。
volatile用法及实现原理
  • 用法:一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的(可见性)
  • 实现原理:有volatile变量修饰的共享变量进行读写操作时,JVM就会向处理器发送一条Lock前缀指令

Lock前缀的指令在多核CPU处理器下会引发以下两件事情

  1. 将当前处理器缓存行的数据写回到系统内存。缓存一致性。
  2. 这个写回到内存的操作会使其他CPU里缓存了该内存地址的数据无效。强制缓存行填充。
synchronized的实现原理与应用

Java中的每一个对象都可以作为锁。

  • 对于普通同步方法,锁是当前实例对象
  • 对于静态同步方法,锁是当前类的Class对象
  • 对于同步方法块,锁是synchronized括号里配置的对象。

应用当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。
实现原理
从JVM规范中可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的实现细节不一样。
代码块同步是使用monitorenter 和monitorexit指令实现的。
方法同步是使用另外一种方式实现的,细节在JVM规范里并没有 详细说明。但是,方法的同步同样可以使用这两个指令来实现。

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。

synchronized锁结论:
同步代码块是依靠两个指令实现的(monitorenter和monitorexit)。
每一个对象都有一个monitor对象关联,一个monitor被持有后,对象会被锁定。
线程执行到monitorenter指令后会去获取monitor所有权,即获取锁。

Java对象头

synchronized用的锁是存在Java对象头里的。
Java对象头长度

长度内容说明
32/64bitMark Word存储对象的hashCode和锁信息
32/64bitClass Metadata Address存储到对象类型数据的指针
32/64bitArray length数组的长度(如果当前对象是数组)
Mark Word

默认存储对象的HashCode、分代年龄和锁标记位。
32位JVM的Mark Word的默认存储结构表

锁状态25bit4bit1bit是否是偏向锁2bit锁标志位
无锁状态对象的hashCode对象的分代年龄001

运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。
Mark Word的状态变化表
在这里插入图片描述
在64位虚拟机下,Mark Word是64bit大小的,其存储结构表
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 中的 volatile 关键字可以保证多线程之间对变量的可见性和有序性,但是它并不能保证原子性。下面是 volatile 底层实现原理: 1. 可见性:当一个变量被 volatile 修饰时,每次修改变量的值时,Java 内存模型都会强制将该变量的值刷新到主内存中,而每次读取该变量时,都会从主内存中读取最新的值。这样就可以保证多个线程之间对变量的修改是可见的。 2. 有序性:volatile 关键字还可以保证变量的读写操作是按照一定的顺序进行的。即当一个线程写入变量时,其它线程在读取该变量之前,必须先从主内存中读取该变量的最新值,而不是使用自己线程的缓存值。这样可以避免出现类似于指令重排的问题。 在底层实现上,volatile 关键字会在变量的读写操作前后插入内存屏障(Memory Barrier)指令,以保证变量的可见性和有序性。 内存屏障分为三种类型: 1. Load Barrier:插入在读操作前面,用于防止读操作重排到内存屏障之后。 2. Store Barrier:插入在写操作后面,用于防止写操作重排到内存屏障之前。 3. Full Barrier:插入在读操作前面和写操作后面,用于防止读操作和写操作重排到内存屏障之后和之前。 在 Java 中,volatile 关键字会在变量的读操作前插入 Load Barrier,而在变量的写操作后插入 Store Barrier。 需要注意的是,虽然 volatile 关键字可以保证多线程之间对变量的可见性和有序性,但是它并不能保证变量的原子性。如果需要保证变量的原子性,可以使用 synchronized 关键字或者 atomic 类来实现。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值