本文主要介绍synchronized的锁实现。
锁的存储
Java中的每一个对象都可以作为锁。synchronized关键字分为如下三种场景:
- 修饰普通方法时,锁住的是调用该方法的对象实例
- 修饰静态方法时,锁住的死当前类的Class对象
- 修饰代码块时,锁住的是括号里的配置的对象
Java对象头内部有一个Mark Word,存储着对象的hashcode、分代年龄、持有偏向锁的线程ID,锁标志位等信息。锁信息就存储在对象头。
锁的分类
锁按照级别从低到高依次是: 无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。锁可以升级但不能降级。
几种锁对应的
锁类型 | 最多参与竞争的线程个数 | 最多线程竞争时各个线程状态 |
---|---|---|
偏向锁 | 1个 | 获取锁 |
轻量级锁 | 2个 | 1个已经获取锁, 一个自旋等待 |
重量级锁 | 大于等于3 | 1个已经获取锁, 其他的阻塞等待 |
无锁
无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。
一般使用CAS来实现。无锁在某些场景下的性能很高。
偏向锁
大部分情况下,锁不仅不存在竞争,而且是由同一个线程多次获取该锁。从而引入了偏向锁的概念。
偏向锁是指当一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,从而降低获取锁的代价。
偏向锁的获取:
- 当一个线程获取锁时, 会在对象头内部记录该线程的ID
- 当该线程退出或者重新获取锁时, 不需要使用CAS进行操作, 只需要简单判断对象头内部是否存储着该线程的ID, 若存在, 则获取锁成功。
偏向锁的释放:
偏向锁并不会自己主动撤销, 只有当有其他线程来竞争锁, 持有偏向锁的线程才会释放偏向锁,撤销之后, 将会恢复到无锁或者轻量级锁状态。
轻量级锁
当锁是偏向锁的时候,且被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
重量级锁
在轻量级锁的状态下,若当前只有一个等待线程,则该线程通过自旋等待。但是出现以下两种情况时,轻量级锁就会升级成重量级锁,此时等待锁的线程进入都会进入阻塞状态。
- 自旋等待线程的自旋次数超过最大值。
- 此时出现第三个线程来竞争锁。
参考资料:
- https://tech.meituan.com/2018/11/15/java-lock.html
- 《并发编程的艺术》