多线程学习记录
synchronized

锁标志位 01:无锁或偏向锁 00:轻量级锁 10:重量级锁 11:GC标志
eThreshold (默认值为40)
假设有线程A和线程B在操作Class X的对象: 初始状态:Class X的所有对象都偏向于线程A。 线程B开始访问这些对象。当它访问第1个被线程A持有的对象时,会触发一次偏向锁撤销(因为不是自己的锁)。JVM会记录:“哦,Class X有一个对象被其他线程竞争了”。 当线程B累计撤销偏向锁的次数达到 BiasedLockingBulkRebiasThreshold(20次) 时,JVM会认为:“看来线程A可能不再使用这个类的大量对象了,而线程B正在成为新的主要使用者”。于是,JVM不会继续撤销,而是启动批量重偏向。
批量重偏向的过程:JVM会将Class X的整个类的偏向锁批量地从重偏向为线程B。这意味着,之后Class X的新实例在创建时,其偏向锁会直接指向线程B,而不再是线程A。对于已经存在的、仍然被线程A持有的对象,它们的偏向锁不会被改变,只有后续新分配的对象和那些因竞争而被撤销后重新加锁的对象,才会以线程B为偏向目标。 简单比喻: 公司有两个项目组(线程A和线程B)共用一批电脑(对象)。一开始所有电脑都贴了“A组专用”(偏向A)。B组偶尔需要借用一台,就撕掉标签(撤销)临时用一下。当B组发现需要借用的次数越来越多(超过20次),老板觉得“A组可能快解散了,B组才是主力”,于是干脆把所有新采购的电脑都直接贴上“B组专用”标签(批量重偏向)。这样既避免了频繁的撕标签,又合理地分配了资源。 3.批量撤销 场景 如果不仅一个类的实例被多个线程交替访问,而且这些线程的访问模式非常混乱,根本无法找到一个明显的“主力线程”,那么批量重偏向也就失去了意义。此时,继续维持偏向锁状态反而会因为频繁的撤销和重偏向带来巨大开销。 触发条件与过程 当针对同一个类,累计发生偏向锁撤销的次数达到了更严格的阈值——BiasedLockingBulkRevokeThreshold(40次)——时,JVM就会判定:“这个类的对象不适合使用偏向锁了”。 批量撤销的过程 1.JVM会全局性地禁用这个类所有实例的偏向锁功能。 2.具体做法是,将这个类的所有实例的锁直接膨胀为轻量级锁(或者更高级别的锁)。之后,无论哪个线程来访问这个类的对象,都会直接进入轻量级锁的竞争流程(通过CAS自旋),而不会再尝试偏向任何一个特定线程。
3.这个类的对象将永远失去使用偏向锁的资格,除非重启JVM或重新设置相关参数。 简单比喻: 接上面的例子,现在不仅是A组和B组,C组、D组也都在抢电脑用,而且毫无规律。老板发现,光是重新贴标签已经完全跟不上混乱的局面了(撤销次数超过40次)。于是他决定:“算了,这批电脑以后不搞‘专用’了,谁要用谁就公平竞争,靠本事抢(轻量级锁的自旋竞争)!” 从此,这个型号的电脑就告别了“偏向”时代。
4.总结
· 理解本质:它们是JVM为了在不同并发场景下自动选择最优锁策略而设计的自适应优化。绝大多数情况下,你不需要手动干预。 · 监控工具:如果你怀疑应用在运行中存在锁竞争问题,可以使用jol(Java Object Layout) 工具查看对象头信息,或使用JVM参数 -XX:+PrintBiasedLockingStatistics来打印偏向锁的统计信息,观察重偏向和撤销的发生情况。
打印对象头信息: ClassLayout.parseInstance(ob).toPrintable()
· 参数调整:在某些极其特殊的场景下(例如,已知某个类的对象会被大量线程交替使用),你可以考虑通过调整 -XX:BiasedLockingBulkRebiasThreshold和 -XX:BiasedLockingBulkRevokeThreshold来微调行为,但这通常是专家级的操作,不建议普通开发者随意修改。 · 关闭偏向锁:如果你的应用启动后存在大量的线程竞争(例如,典型的Web服务器),开启偏向锁的初始延迟(-XX:BiasedLockingStartupDelay=0)和后续的批量撤销可能会带来不必要的性能波动。在这种情况下,可以考虑在启动时直接关闭偏向锁:-XX:-UseBiasedLocking,让所有锁直接从无锁或轻量级锁开始,可能会获得更稳定的性能。
