锁升级
JVM 将 synchronized 锁分为四个状态: 无锁, 偏向锁, 轻量级锁, 重量级锁.
JVM 会根据清空, 自动进行锁升级.
偏向锁
偏向锁不是真的 “加锁”, 只是给锁住的资源做了一个标记, 记录这个锁属于哪个线程.
如果后续没有其他线程来竞争锁, 那么就不用进行其他操作 (减少了加锁解锁的开销)
如果后续有其他线程来竞争锁, 就会根据标记, 取消 偏向锁状态, 进入轻量级锁状态
轻量级锁
轻量级锁即自适应的自旋锁
synchronized 的轻量级锁是通过 CAS 来实现
- 通过 CAS 检查并更新一块内存
- 如果更新成功, 则认为加锁成功
- 如果更新失败, 则认为锁被占用, 继续自旋式等待(并不放弃 CPU, 以便解锁后能更快获得锁)
自适应的含义
自旋锁是一直让 CPU 空转, 比较浪费 CPU 资源
自适应的意思是 : 自旋不会一直持续进行, 到达一定的时间/重试次数, 就停止自旋(放弃 CPU 等待)
重量级锁
自旋锁经过一定时间/重试次数, 依旧不能获取锁, 就会锁升级为重量级锁
此处的重量级锁就是指用到内核提供的 mutex.
- 执行加锁操作, 先进入内核态
- 在内核态判定当前锁是否已被占用
- 如果锁未被占用, 则加锁成功, 并切换回用户态
- 如果该锁被占用, 则加锁失败. 此时线程进入锁的等待队列, 挂起. 等待 操作系统 唤醒
- 当这个锁被其他线程释放, 操作系统唤醒等待队列中挂起的线程, 该线程尝试获取锁
锁消除
编译器 和 JVM 对于代码的优化操作
如果编译器 + JVM 判定你的代码中加的锁没有用到 (比如单线程环境下的加锁操作), 就会自动消除锁
eg :
StringBuffer sb = new StringBuffer(); sb.append("a"); sb.append("b"); sb.append("c"); sb.append("d");
StringBuffer.append() 涉及加锁解锁操作, 但是如果在单线程环境下运行此代码, 那么这些加锁解锁操作就会浪费不必要的资源, 就会被自动消除掉(编译阶段
)
锁粗化
锁的粒度 :
单个锁所保护资源的数据量, 我们以 粗
和 细
来表示
锁粗化就是: 一段逻辑中多次出现加锁解锁操作, 编译器 + JVM 就会自动将这些操作合并为一个加锁解锁操作 (加锁和解锁操作涉及资源的消耗)
下面代码就是把50000次加锁解锁操作合并为一个加锁解锁操作, 节省了很多资源消耗