Java内置锁synchronized的膨胀升级机制,锁的粗化和消除

Java中锁是比较难的一块,包括synchronized,如加锁之后对象头信息的变化是怎样的?更不用说AQS。不要一开始就纠结共享锁/独占锁,公平锁/非公平锁,以及偏向等,应该从原理层面去理解,一劳永逸。

Synchronized

在JDK1.6之前,synchronized重度依赖于操作系统,涉及到CPU状态切换,效率很低。(据说Doug Lea因此才开发了AQS框架)。JDK1.6才有了较大的优化,引入了锁的膨胀升级机制。

查看对象头信息的工具:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.16</version>
</dependency>

32位虚拟机对象头组成参考:
Java内置锁synchronized_4s

synchronized的用法

  1. 修饰指定对象
// 锁住的只是obj这个对象
Object obj = new Object();

synchronized (obj) {
  System.out.println("...");

  // synchronized是可重入的, 这里实际上会被优化掉
  synchronized (obj) {
    System.out.println("***");
  }
}
  1. 修饰普通方法
// 锁住的是具体的实例
public class Test {
  public synchronized void print() {
    System.out.println("...");
  }
}
  1. 修饰静态方法
// 锁住的是当前类,静态成员属于类
public class Test {
  public static synchronized void print() {
    System.out.println("...");
  }
}

synchronized的升级过程

  • 无锁:程序运行即刻创建的对象(4s内)
  • 偏向锁:默认情况,在程序启动4s后创建的对象,JVM会自动为这些对象加偏向锁。延迟4s是为了避开程序启动过程中的资源竞争。
  • 轻量级锁:多线程竞争不激烈
  • 重量级锁:多线程竞争资源激烈

锁的升级过程是不可逆的。偏向锁不是真正加锁,偏向锁和无锁状态是可以转换的。另外,调用hashCode()会导致锁升级,无法重新进入偏向状态,涉及对象头的结构,很复杂。

synchronized的粗化

Object obj = new Object();

synchronized (obj) {
  synchronized (obj) {
    System.out.println("***");
  }
}

这样加锁代码可以执行,但实际上里层的synchronized是完全不必要的,只需留下外层的synchronized即可,而JVM就会做这种优化,即锁的粗化。

synchronized的消除

Object obj = new Object();

synchronized (obj) {
  synchronized (obj) {
    System.out.println("***");
  }
}

如果本身程序就是单线程的,不需要锁,JVM也会优化,将锁去掉,即锁的消除。实际上粗话就消除了里层的锁。

由上述内容可以得知,synchronized是可重入锁。另外,多个线程来synchronized(obj)的时候,synchronized无法对线程进行排队,而是线程去抢,所以是非公平的。