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位虚拟机对象头组成参考:
synchronized的用法
- 修饰指定对象
// 锁住的只是obj这个对象
Object obj = new Object();
synchronized (obj) {
System.out.println("...");
// synchronized是可重入的, 这里实际上会被优化掉
synchronized (obj) {
System.out.println("***");
}
}
- 修饰普通方法
// 锁住的是具体的实例
public class Test {
public synchronized void print() {
System.out.println("...");
}
}
- 修饰静态方法
// 锁住的是当前类,静态成员属于类
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
无法对线程进行排队,而是线程去抢,所以是非公平的。