JVM 将 synchronized 锁分为 无锁、偏向锁、轻量级锁、重量级锁状态。会根据情况,进行依次升级。
第一个尝试加锁的线程, 优先进入偏向锁状态.
偏向锁本质上相当于 “延迟加锁” . 能不加锁就不加锁, 尽量来避免不必要的加锁开销. 但是该做的标记还是得做的, 否则无法区分何时需要真正加锁.
随着其他线程进入竞争, 偏向锁状态被消除, 进入轻量级锁状态(自适应的自旋锁).
此处的轻量级锁就是通过 CAS 来实现.
自旋操作是一直让 CPU 空转, 比较浪费 CPU 资源.
因此此处的自旋不会一直持续进行, 而是达到一定的时间/重试次数, 就不再自旋了.
也就是所谓的 “自适应”
如果竞争进一步激烈, 自旋不能快速获取到锁状态, 就会膨胀为重量级锁
此处的重量级锁就是指用到内核提供的 mutex .
一段逻辑中如果出现多次加锁解锁, 编译器 + JVM 会自动进行锁的粗化.
锁的粒度: 粗和细, 指加锁代码所涉及到的范围, 涉及的范围越大, 锁的粒度越粗, 涉及的范围越小,锁的粒度越细,
Object locker = new Object();
for (int i = 0; i < 100; i++) {
synchronized (locker) {
}
}
synchronized (locker) {
for (int i = 0; i < 100; i++) {
}
}
第一段代码的锁的粒度就比较细, 第二段代码锁的粒度就比较粗
举个栗子:
领导给下属交代工作任务:
方式一:
方式二:
显然, 方式二是更高效的方案.
到底锁粒度粗好还是细好 ?
各有各的好:
编译器就会有一个优化, 自动判定, 如果某个地方锁粒度太细可能就会粗化, 避免频繁申请释放锁.
有些地方明明不用加锁却加上了锁, 编译器+JVM 判断锁是否可消除. 如果可以, 就直接消除.
注意: 锁消除和偏向锁不一样, 锁消除是不该加锁的地方但是加了, 偏向锁是需要加锁, 但是并没有真正的加锁,等.
有些应用程序的代码中, 用到了 synchronized, 但其实没有在多线程环境下. (例如 StringBuffer)
StringBuffer stringBuffer= new StringBuffer();
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
stringBuffer.append("d");
此时每个 append 的调用都会涉及加锁和解锁. 但如果只是在单线程中执行这个代码, 那么这些加锁解锁操作是没有必要的, 白白浪费了一些资源开销.
什么是偏向锁?
偏向锁不是真的加锁, 而只是在锁的对象头中记录一个标记(记录该锁所属的线程). 如果没有其他线
程参与竞争锁, 那么就不会真正执行加锁操作, 从而降低程序开销. 一旦真的涉及到其他的线程竞争, 再取消偏向锁状态, 进入轻量级锁状态.
synchronized 实现原理 是什么?
上面所有的内容
好啦! 以上就是对 Synchronized 原理的讲解,希望能帮到你 !
评论区欢迎指正 !