一文带你了解synchronized的各种锁,这些锁是如何变化的,什么样的操作会导致锁发生变化?

内存布局对应对应的锁状态

java 偏向锁失效 偏向锁 重入_sed

先说锁状态的变化结论

java 偏向锁失效 偏向锁 重入_sed_02

偏向锁

  • 偏向锁是一种针对加锁操作的优化手段。
  • 在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了消除数据在无竞争情况下锁重入(CAS操作)的开销而引入偏向锁。
  • 对于没有锁竞争的场合,偏向锁有很好的优化效果。
  • JVM启用了偏向锁模式:jdk6之后默认开启
  • 新创建对象的Mark Word中的Thread Id为0,说明此时处于可偏向但未偏向任何线程,也叫做匿名偏向状态(anonymously biased)。
偏向锁延迟偏向
  • HotSpot 虚拟机在启动后开启偏向锁模式默认在4s后。
  • 为了减少初始化时间,JVM默认延时加载偏向锁。
//关闭延迟开启偏向锁
-XX:BiasedLockingStartupDelay=0
//禁止偏向锁
-XX:-UseBiasedLocking

java 偏向锁失效 偏向锁 重入_sed_03

  • 上图的代码可以验证:从无锁变为偏向锁(4秒)
偏向锁在无竞争的时候一直是偏向锁
public static void main(String[] args) throws InterruptedException {
		log.debug(Thread.currentThread().getName() + "最开始的状态。。。\n"
				+ ClassLayout.parseInstance(new Object()).toPrintable());
		// HotSpot 虚拟机在启动后有个 4s 的延迟才会对每个新建的对象开启偏向锁模式
		Thread.sleep(4000);
		Object obj = new Object();

		new Thread(new Runnable() {
			@Override
			public void run() {
				log.debug(
						Thread.currentThread().getName() + "开始执行准备获取锁。。。\n" + ClassLayout.parseInstance(obj).toPrintable());
				synchronized (obj) {
					log.debug(Thread.currentThread().getName() + "获取锁执行中。。。\n"
							+ ClassLayout.parseInstance(obj).toPrintable());
				}
				log.debug(Thread.currentThread().getName() + "释放锁。。。\n" + ClassLayout.parseInstance(obj).toPrintable());
			}
		}, "thread1").start();

		Thread.sleep(5000);
		log.debug(Thread.currentThread().getName() + "结束状态。。。\n" + ClassLayout.parseInstance(obj).toPrintable());
	}
  • 执行结果

java 偏向锁失效 偏向锁 重入_java 偏向锁失效_04

  • 从结果可以看出:无锁状态经过4秒变为偏向锁,之后的的状态一直是偏向锁!
  • 在进入同步代码块后,锁的偏向线程由0变为具体的线程。
在同步代码块外调用hashCode()方法

java 偏向锁失效 偏向锁 重入_sed_05

  • 进入同步代码块后锁升级为轻量级锁
  • 当对象可偏向(线程ID为0)时,MarkWord将变成未锁定状态,并只能升级成轻量锁。
在同步代码块内调用hashCode()方法

java 偏向锁失效 偏向锁 重入_java 偏向锁失效_06

  • 直接升级为重量级锁
  • 当对象正处于偏向锁时,调用HashCode将使偏向锁强制升级成重量锁。
偏向锁撤销:自己验证wait和notify
  • 调用锁对象的obj.hashCode()或System.identityHashCode(obj)方法会导致该对象的偏向锁被撤销。
  • 因为对于一个对象,其HashCode只会生成一次并保存,偏向锁是没有地方保存hashcode的。
  • 轻量级锁会在锁记录中记录 hashCode。
  • 重量级锁会在 Monitor 中记录 hashCode。
  • 当对象可偏向(线程ID为0)时,MarkWord将变成未锁定状态,并只能升级成轻量锁。
  • 当对象正处于偏向锁时,调用HashCode将使偏向锁强制升级成重量锁。
  • 偏向锁状态执行obj.notify() 会升级为轻量级锁。
  • 调用obj.wait(timeout) 会升级为重量级锁。

轻量级锁

  • 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段,此时Mark Word 的结构也变为轻量级锁的结构。
  • 轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间多个线程访问同一把锁的场合,就会导致轻量级锁膨胀为重量级锁。
  • 轻量级锁在降级的时候直接变为无锁状态!(查看之前在同步代码块外调用hashCode()方法)
模拟竞争不激烈的场景
@Slf4j
public class TestMemory {

	public static void main(String[] args) throws InterruptedException {
		log.debug(Thread.currentThread().getName() + "最开始的状态。。。\n"
				+ ClassLayout.parseInstance(new Object()).toPrintable());
		// HotSpot 虚拟机在启动后有个 4s 的延迟才会对每个新建的对象开启偏向锁模式
		Thread.sleep(4000);
		Object obj = new Object();

		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				log.debug(Thread.currentThread().getName() + "开始执行thread1。。。\n"
						+ ClassLayout.parseInstance(obj).toPrintable());
				synchronized (obj) {
					log.debug(Thread.currentThread().getName() + "获取锁执行中thread1。。。\n"
							+ ClassLayout.parseInstance(obj).toPrintable());
				}
				log.debug(Thread.currentThread().getName() + "释放锁thread1。。。\n"
						+ ClassLayout.parseInstance(obj).toPrintable());
			}
		}, "thread1");
		thread1.start();

		// 控制线程竞争时机
		Thread.sleep(1);

		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				log.debug(Thread.currentThread().getName() + "开始执行thread2。。。\n"
						+ ClassLayout.parseInstance(obj).toPrintable());
				synchronized (obj) {
					log.debug(Thread.currentThread().getName() + "获取锁执行中thread2。。。\n"
							+ ClassLayout.parseInstance(obj).toPrintable());
				}
				log.debug(Thread.currentThread().getName() + "释放锁thread2。。。\n"
						+ ClassLayout.parseInstance(obj).toPrintable());
			}
		}, "thread2");
		thread2.start();

		Thread.sleep(5000);
		log.debug(Thread.currentThread().getName() + "结束状态。。。\n" + ClassLayout.parseInstance(obj).toPrintable());
	}
}
竞争不激烈的场景的运行结果

java 偏向锁失效 偏向锁 重入_java 偏向锁失效_07

重量级锁

  • 轻量级锁经过一次自选如果没有获取到锁,直接膨胀为重量级锁。
  • 重量级锁是基于 Monitor 机制,并且在 Monitor 中记录 hashCode
模拟竞争激烈的场景
  • 去掉不激烈的场景中的以下代码就是竞争激烈的场景
// 控制线程竞争时机
Thread.sleep(1);
竞争激烈的场景的运行结果

java 偏向锁失效 偏向锁 重入_公众号_08