Java锁简述

锁的状态总共有四种:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。这几个状态会随着竞争情况逐渐升级。为了提高获得锁和释放锁的效率,锁可以升级但不能降级。

偏向锁

为了在无多线程竞争的情况下减少线程获得锁的代价,引入了偏向锁的概念。

偏向锁只需要在切换线程ID的时候进行CAS操作,之后该线程在进入和退出同步块的时候不再需要进行CAS操作来加锁与解锁。因此偏向锁可以提高单一线程多次操作同步块的性能。

线程B要使用一个同步对象的时候,会查询对象头里的Mark Word的锁状态是否为可偏向状态。如果是,测试线程ID是否指向本线程。

  • 如果是,则表示线程B已经获得锁了,执行同步代码即可。
  • 如果不是,则通过CAS操作来竞争偏向锁(首先会暂停拥有偏向锁的线程A,检查持有偏向锁的线程A是否活着,如果线程A不处于活动状态,则竞争成功;如果线程A仍然存活,则表明存在竞争关系,竞争失败。)
    • 如果竞争成功,则将Mark Word设置为无锁状态,然后将Mark Word重新设置偏向锁,Thread ID指向线程B
    • 如果竞争失败,则表明有竞争关系。当到达全局安全点的时候,获得偏向锁的进程A会被挂起,偏向锁会升级为轻量级锁,然后被挂起的进程A会继续向下执行,进程B通过自旋竞争锁。

偏向锁只有当有其他线程竞争锁的时候才会释放,偏向锁的撤销会在全局安全点的时候进行。

轻量级锁

线程执行同步块之前,会在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的Mark Word复制到锁记录中,官方称为Displaced Mark Word。线程通过CAS尝试将对象头中的Mark Word替换为指向锁记录的指针。如果成功,线程获得锁;如果失败,表示有其他线程竞争锁了,当前线程会尝试使用自旋来获得锁。

如果自旋失败,轻量级锁会膨胀为重量级锁。

轻量级解锁时,会使用CAS操作将Displaced替换回到对象头。如果成功则表示没有竞争关系,如果失败(失败的原因是Mark Word的锁标志位被修改导致CAS失败),表示当前锁存在竞争关系,且锁已经被升级为重量级锁

重量级锁

重量级锁下,当线程试图获取锁的时候,线程会被阻塞,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程之间会进行锁的争夺。

锁状态之间的转换概括

如果多个线程不存在竞争同步块的问题,则对象会保持偏向锁的状态。当出现两个线程同时竞争同步块的时候,锁会升级为轻量级锁,后来的线程会通过自旋等待拥有锁的进程执行完同步代码。如果成功则将Mark Word替换成指向栈中锁记录的指针。如果失败多次则表示有其他线程竞争锁,则膨胀为重量级锁。

CAS操作

CAS非常重要,理解了CAS的概念才能知道上面的操作成功失败的真正意义。

CAS原理:CAS有三个操作数,即内存值v,旧的操作数a,新的操作数b。当我们需要更新v对应的内存地址中的值为b时,首先判断内存中的值是否和我们之前的所见值a相同,若相同则将v对应的内存地址保存的值修改为b,若不同,则什么都不做。

也可以理解旧操作数为期望值,当期望值与对应内存中的值相同时,视为满足期望。此时才将b值写入内存值v对应的内存。

锁的优缺点对比
优点 缺点 适用场景
偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 适用于只有一个线程访问同步块场景
轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度。 如果始终得不到锁竞争的线程使用自旋会消耗CPU。 追求响应时间。同步块执行速度非常快。
重量级锁 线程竞争不使用自旋,不会消耗CPU。 线程阻塞,响应时间缓慢。 追求吞吐量。同步块执行速度较长。

你可能感兴趣的:(Java锁简述)