Java锁的升级策略 偏向锁 轻量级锁 重量级锁

这三种锁是指锁的状态,而且是专门针对Synchronized关键字。JDK 1.6 为了减小"重量级锁"的性能消耗,引入了“偏向锁”和“轻量级锁”,锁一共拥有4种状态:无锁状态、偏向锁、轻量级锁、重量级锁。锁状态是经过对象头的Mark Word来进行标记的:安全

锁能够升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁,这种锁升级却不能降级的策略,是为了提升得到锁和释放锁的效率多线程

  1. 重量级锁:依赖于底层操做系统的Mutex Lock,线程会被阻塞住
    • 缺点:加锁和解锁须要从用户态切换到内核态,性能消耗较大
  2. 轻量级锁:基于重量级锁进行了优化(避免上下文切换,提升了性能),它假设多线程竞争是互相错开的,不会发生线程阻塞,呢么上下文切换就是多余的
    • 第一个特色:采用了CAS操做加锁和解锁,因为轻量级锁的锁记录(Lock Record)是存放在对象头和线程空间里的,所以加锁和解锁不须要上下文切换,性能消耗较小
    • 第二个特色:一旦发生多线程竞争,首先基于“自旋锁”思想,自旋CPU循环等待一段时间,不会发生上下文切换,若是仍是没法得到锁,就将锁升级为重量级锁
  3. 偏向锁:基于轻量级锁进行了优化(减小屡次的加锁和解锁,提升了性能),它假设整个过程只有一个线程得到锁,呢么屡次的加锁和解锁就是多余的
    • 特色:在第一次得到锁以后不会释放锁,它会一直持有锁,后续进入锁时只需检查一下锁状态和偏向线程ID是否为本身,从而省去了屡次的加锁和解锁

1.偏向锁

获取锁:性能

  1. 检测对象头的Mark Word是否为可偏向状态(便是否为偏向锁1,锁标志位是否为01),若是不是,尝试竞争锁:尝试CAS操做将Mark Word的线程ID设置为当前线程ID,以表示线程得到锁,若是失败说明锁已被占用
  2. 若为可偏向状态,则检查线程ID是否为当前线程ID,若是是则表示当前线程已经持有锁(锁的可重入),不然说明锁已被占用
  3. 若是锁已被占用,只能撤销偏向锁为无锁状态或轻量级锁

释放锁:(偏向锁使用了一种等到竞争出现才释放锁的机制,线程是不会主动释放偏向锁的,只有当其余线程竞争偏向锁时,持有偏向锁的线程才会释放锁。这样作的好处是大大减小了加锁和解锁的次数,提高性能)优化

  1. 偏向锁的撤销须要等待全局安全点(在这个时间点没有正在执行的字节码),暂停拥有偏向锁的线程,检查持有偏向锁的线程是否还活着
  2. 若是线程挂了,则将对象头设置成无锁状态;若是线程仍然活着,则将对象头设置为轻量级锁(锁的升级),最终轻量级锁必定会被释放

2.轻量级锁

获取锁:操作系统

  1. 检测对象头的Mark Word是否为轻量级锁(锁标志位为00),若是不是,尝试竞争锁:JVM首先在当前线程的栈帧中创建一个锁记录(Lock Record),用于备份存储对象头的Mark Word(官方把这份拷贝加了一个Displaced前缀,称为Displaced Mark Word),而后JVM尝试CAS操做将Mark Word更新为指向Lock Record的指针,以表示线程得到锁,若是失败说明锁已被占用
  2. 若为轻量级锁,判断对象头的Mark Word是否指向当前线程的栈帧的Lock Record,若是是则表示当前线程已经持有锁(锁的可重入),不然说明锁已被占用
  3. 若是锁已被占用,当前线程便尝试自旋CPU来获取锁,自旋必定次数后轻量级锁会膨胀为重量级锁(锁标志位变成10),线程进入阻塞

释放锁:线程

  1. 尝试CAS操做将Displaced Mark Word中替换回对象头,若是成功,说明轻量级锁释放成功
  2. 若是CAS操做失败,说明存在锁竞争,锁已经膨胀成重量级锁,须要在释放锁的同时唤醒那些被挂起的线程

3.重量级锁

重量级锁依赖于底层操做系统的Mutex Lock,全部线程都会被阻塞住,线程之间的切换须要从用户态到内核态,切换成本很是高。指针

总结:锁的优缺点对比

优势 缺点 适用场景
偏向锁(Biased Lock) 加锁和解锁不须要额外的消耗,和执行非同步方法相比仅存在纳秒级的差距 若是线程间存在锁竞争,会带来额外的锁撤销 适用于只有一个线程访问
轻量级锁(Lightweight Lock) 竞争的线程不会阻塞,提升了程序的响应速度 对于得不到锁的线程,自旋会消耗CPU 追求响应时间,或者要求临界区简短,自旋不会占用CPU太久
重量级锁(Heavyweight Lock) 线程竞争不使用自旋,不会消耗CPU资源 线程阻塞,响应时间缓慢 追求吞吐量
相关文章
相关标签/搜索