您的位置:首页 > 编程语言 > Java开发

(二)Java并发机制底层实现原理

2020-06-02 06:31 495 查看

1. volatile的原理

volatile,轻量级的synchronized,保证的是一个线程修改后,另一个线程立马可见的可见性,不会引起上下文切换。

底层实现
volatile Singleton instance = new Singleton()

JIT编译器生成的指令代码如下:

0x01a3deld:
mov $0x0,0x1104800(%esi);
0x01a3de24: lock addl $0x0, (%esp);

有volatile修饰的变量进行写操作时会多出第二行汇编代码,lock指令在多核处理器下产生两个结果:

  1. 将当前处理器缓存的数据写回系统内存;
  2. 写回内存操作使其它CPU里缓存了该内存地址的数据无效,导致重新从系统内存读取;

以上操作导致了该变量的修改在不同线程之间是可见的。

2. synchronized的原理与应用

synchronized是重量级锁,随着Java1.6进行优化以后,引入了偏向锁、轻量级锁,以及锁的存储结构和升级过程。
synchronized锁有三种形式:

  1. 普通同步方法,锁是当前实例;
  2. 静态同步方法,锁是当前Class对象;
  3. 同步方法块,锁是括号里配置的对象;

synchronized是由JVM实现的,代码块同步是通过编译后在代码开始和结束(包括异常)位置分别使用monitorenter和monitorexit指令来实现的,同步方法则是方法属性里加入同步标记来实现。任何对象都有一个monitor与之关联。

1)Java对象头

synchronized使用的锁是存在Java对象头里的,对象为数组类型,则虚拟机使用3个字宽(32位虚拟机一个字宽为32位,即4个字节),非数组类型使用2个字宽存储对象头。

长度 内容 说明
32/64 bit Mark Word 对象的hashCode、锁信息
32/64 bit Class Metadata Address 对象类型数据的地址
32/64 bit Array Length 数组长度(数组类型专有)

MarkWord的存储结构:

锁状态 25bit 4bit 1bit 是否偏向锁 2bit 锁标志位
无锁状态 对象HashCode 对象分代年龄 0 01

2)锁升级

Java 1.6为了减少锁获取和释放带来的性能损耗,引入了“偏向锁”和“轻量级锁”,级别从低到高:无锁->偏向锁->轻量级锁->重量及锁。锁可以升级,但不能降级。

  • . 偏向锁
    一个线程访问同步块时,会在对象头和栈帧的锁记录里存储当前线程ID,以后该线程在进入和退出时不需要CAS来加解锁,只需简单测试MarkWord里是否有偏向锁指向自己。
  • 轻量级锁
    加锁:执行同步块之前,JVM先在当前线程栈帧中创“锁记录”空间,并将MarkWord复制到“锁记录”中,然后尝试使用CAS将对象头中MarkWord替换为指向“锁记录”的指针,成功则获取锁,失败则使用“自旋”来获取锁。
    解锁:使用原子操作将“锁记录”替换回对象头,成功则表示没有竞争,失败表示存在竞争,锁膨胀为重量级锁。

3. 原子操作实现原理

1)处理器如何实现原子锁
  • 总线锁定
    使用处理器提供的Lock#信号,一个处理器输出此信号时,其他处理器请求将被阻塞。锁定开销较大。
  • 缓存锁定
    内存区域被缓存在处理器的缓存行中,Lock期间被锁定,执行回写内存时(缓存一致性会阻止同时修改由两个处理器缓存的内存区),会使其他缓存了该内存区的数据无效,导致重新读取内存。
2) Java中原子操作

实现方式:

  • 锁:如之前内容所述,获取锁的线程才能操作指定共享的内存区域;
  • 循环CAS:使用了处理器提供的CMPXCHG指令;

循环CAS存在的问题:
1. ABA问题,解决方法:版本号机制,每次操作版本号+1;
2. 循环时间长,开销大;
3. 只能保证一个变量的原子操作;

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: