JAVA基础 - 锁

此文需要结合 JAVA基础 - synchronized 一起看

1. 锁的分析

  1. 悲观锁
  2. 乐观锁
  3. synchronize 锁升级
    1. 无锁
    2. 偏向锁
    3. 轻量级锁

1.悲观锁 (ReentrantLock synchronized 等)
描述: 总是假设最坏的情况,每次去拿数据的时候都任务别人会修改,所以每次在拿数据的时候都会上锁,
这样别人想拿这个数据就会阻塞,因为synchronized我们也成为悲观锁。

2.乐观锁
描述:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,就算改了也没有关系。
再重试即可。所以不会上锁。但是在更新的时候回判断一下在此期间别人有没有修改这个数据。
如果没有人修改则更新。如果有人修改则重试。

CAS 这种机制 我们也成为乐观锁。

CAS获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。结合CAS和
volatile 可以实现无锁并发,适合竞争不激烈,多核CPU的场景下
1.因为没有使用synchronize,所以线程不会陷入阻塞,这是效率提升的因素之一。
2.如果竞争激烈,可以想到重试必然频繁发生,反而效率会影响。

总结:

***CAS的作用? Compare And  Swap .  CAS 可以将比较和交换为原子操作。这个原子操作
直接由处理器保证。***

***CAS的原理? CAS 需要三个值:内存地址V, 旧的预期值A,需要修改的新值B,如果内存地址V和旧的预期值
A相等就修改内存地址为B。***

synchronize 锁升级

 说锁必须先认清 对象JVM中的内部结构.
 问题1:锁存在哪里?


 **在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充**

在这里插入图片描述

前置知识(未完成 – 优化点 表格化)

对象头

当一个线程尝试访问synchronize修饰的代码块时,它首先要获得锁。那么这个锁到底存在哪里?
答案:存在锁对象的对象头中
HotSpot采用 instanceOopDesc和arrayOopDesc来描述对象头,
arrayOopDesc对象用来描述数组类型,instanceOopDesc的定义在Hotspot源码的instanceOop.hpp文件中,另外 arrayOopDesc中定义对应的arrayOop.hpp

class instanceOopDesc : public oopDesc {
 public:
// aligned header size.
  static int header_size() { return sizeof(instanceOopDesc)/HeapWordSize; }
// If compressed, the offset of the fields of the instance may not be aligned.
  static int base_offset_in_bytes() {
    // offset computation code breaks if UseCompressedClassPointers
    // only is true
    return (UseCompressedOops && UseCompressedClassPointers) ?
             klass_gap_offset_in_bytes() :
             sizeof(instanceOopDesc);
}
  static bool contains_field_offset(int offset, int nonstatic_field_size) {
    int base_in_bytes = base_offset_in_bytes();
    return (offset >= base_in_bytes &&
} };

从instanceOopDesc代码中可以看到instanceOop继承oopDesc,oopDesc的定义载HotSpot源码文件oop.hpp文件中

class oopDesc {
  friend class VMStructs;
 private:
  volatile markOop  _mark;
  union _metadata {
    Klass*      _klass;
    narrowKlass _compressed_klass;
  } _metadata;
// Fast access to barrier set. Must be initialized.
  static BarrierSet* _bs;
// 省略其他代码 
};

在普通实例对象中,oopDesc的定义包含了两个成员,分别就是_mark 和 _metadata 、

_mark 表示对象标记,属于markOop类型,也就是接下来要讲解的Mark World,它记录了对象和锁有关信息

_metadata表示元信息,类元信息存储的是对象指向它的类元数据(klass)的首地址,其中klass 表示 普通指针,_compressed_klass 表示压缩类指针。

Mark Word

Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode) , GC分代年龄,锁状态标志,线程持有锁,偏向线程ID,偏向时间戳等。
占用内存大小与虚拟机位长一致。Mark Word 对应的类型 markOop
具体可见 markOop.hpp 注释描述。

64bit 大小 存储结构:
在这里插入图片描述

Klass pointer

这一部分用于存储对象的类型指针,该指针指向它的类元数据,jvm通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64的JVM将会比32位的JVM多耗费50的内存。为了节约内存可以使用选项 -XX:+UseCompressedOops 开启指针压缩。其中 oop即ordinary object pointer 普通对象指针。

实例数据

类中定义的成员变量

对齐填充

对齐填充并不是必然存在的,也没有什么特别的意义,他仅仅起着占位符的作用,由于HotSpot VM的 自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的 整数倍。而对象头正好是8字节的倍数,因此,当对象实例数据部分没有对齐时,就需要通过对齐填充 来补全。

锁 - 正文知识

偏向锁

描述:锁会偏向于第一个获得它的线程,会在对象头存储锁偏向的线程ID,以后该线程进入和退出同步块只需要检查是否为偏向锁,锁标志位以及ThreadId 即可。

缺点: 不过一旦出现多个线程竞争时必须撤销偏向锁,所以撤销偏向锁消耗的性能必须小于之前节省下来CAS原子操作的性能消耗,不然就得不偿失。

偏向锁的撤销

1.偏向锁的撤销动作必须等待全局安全点
2.暂停拥有的偏向锁的线程,判断锁对象是否处于被锁定状态
3. 撤销偏向所,恢复到无锁(标志位01) 或者轻量锁(标志位00)的状态

偏向锁的好处

偏向锁是在只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一锁的情况。偏向 锁可以提高带有同步但无竞争的程序性能。
它同样是一个带有效益权衡性质的优化,也就是说,它并不一定总是对程序运行有利,如果程序中大多 数的锁总是被多个不同的线程访问比如线程池,那偏向模式就是多余的。

小结

偏向锁的原理是什么?

当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设为“01”,即偏向模式。同时使用CAS操 作把获取到这个锁的线程的ID记录在对象的Mark Word之中 ,如果CAS操作成功,持有偏向锁的线程以后每 次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作,偏向锁的效率高。

偏向锁的好处是什么?

偏向锁是在只有一个线程执行同步块时进一步提高性能,适用于一个线程反复获得同一锁的情况。偏向锁可以 提高带有同步但无竞争的程序性能。

轻量级锁(持续优化)

描述:在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗,但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁, 所以轻量级锁的出现并非是要代替重量级锁。

当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级别锁,则会尝试获取轻量级锁。
获取步骤:
1. 判断当前对象是否处于无锁状态(hashcode 0, 01 ) , 如果是,则JVM首先将在当前线程的栈帧中
建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,将对象的Mark Word复制到栈帧中的Lock Record中,将Lock Reocrd中的owner指向当前对象。
2. JVM利用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,如果成功表示竞争到 锁,则将锁标志位变成00,执行同步操作。
3. 如果失败则判断当前对象的Mark Word是否指向当前线程的栈帧,如果是则表示当前线程已经持 有当前对象的锁,则直接执行同步代码块;否则只能说明该锁对象已经被其他线程抢占了,这时轻 量级锁需要膨胀为重量级锁,锁标志位变成10,后面等待的线程将会进入阻塞状态。

轻量级锁的释放

  1.取出在获取轻量级锁保证在MarkWord中的数据。
  2.用CAS操作将取出的数据替换当前对象的Mark Word中,如果成功,则说明释放锁成功。
  3.如果CAS操作替换失败,说明其他线程尝试获取该锁,则需要将轻量级锁需要升级为重量锁。

好处

在多线程交替执行同步代码块的情况下,可以避免重量级锁引起的性能消耗

轻量级锁的原理时什么?
将对象的Mrk Word 复制到栈帧中的Lock Recod中。mark Word 更新为指向Lock Record的指针。

自旋锁

描述: 两个或者多个线程同时并行执行,自选可以让后面请求锁的那个线程 不断验证是否通行
,但是不放弃处理器执行时间,让线程出于等待状态。 默认次数时 10次。

优势:

避免线程的阻塞和唤醒,频繁的操作让CPU 用户态 和 核心态 交互压力变大。

劣势:

如果线程占用时间占用时间较长,那自旋线程就会浪费性能,一直做无用功。
©️2020 CSDN 皮肤主题: 书香水墨 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值