1. 重量级锁的核心原理
JVM中每个对象都会有一个监视器,监视器和对象一起创建、销毁。监视器相当于一个用来监视这些线程进入的特殊房间,其义务是保证(同一时间)只有一个线程可以访问被保护的临界区代码块
- 监视器所保护的临界区代码是互斥地执行的,线程进入临界区代码都需要获得这个许可,离开时把许可归还
- 监视器提供Signal机制(正持有许可的线程暂时放弃许可进入阻塞等待状态,等待其他线程发送Signal去唤醒 拥有许可的线程可以发送Signal,唤醒正在阻塞等待的线程,让它可以重新获得许可并启动执行)
Hotspot虚拟机中,监视器是由C++类ObjectMonitor实现的, 其有3个比较重要的属性:
- Cxq:竞争队列(Contention Queue),所有请求锁的线程首先被放在这个竞争队列中
- EntryList:Cxq中那些有资格成为候选资源的线程被移动到EntryList中
- WaitSet:某个拥有ObjectMonitor的线程在调用Object.wait()方法之后将被阻塞,然后该线程将被放置在WaitSet链表中
2. 重量级锁的开销
重量级锁会发生线程的上下文切换,从用户态切换到内核态。系统调用是内核态为用户态进程提供的Linux内核态下互斥锁的访问机制,系统调用就会从用户态切换到内核态,这种切换是需要消耗很多时间的,而JVM重量级锁使用了Linux内核态下的互斥锁。
3. 代码实例
package innerlock;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
public class InnerLockTest {
int a=1;
double b=1.1;
public static void main(String[] args) throws InterruptedException {
System.out.println(VM.current().details());
Object obj=new Object();
new Thread(()->{
synchronized (obj) {
System.out.println("t1:"+ClassLayout.parseInstance(obj).toPrintable());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(1000);
new Thread(()->{
synchronized (obj) {
System.out.println("t1:"+ClassLayout.parseInstance(obj).toPrintable());
}
}).start();
Thread.sleep(2000);
}
}
上述代码中t1线程先获取到锁,此时是偏向锁,当t1线程还没有离开临界区时,此时线程t2也来抢夺锁资源(t2可能经过多次自旋操作,但是失败了),因此锁升级为重量级锁
4. 偏向锁、轻量级锁与重量级锁的对比
参考:《Java高并发编程卷2》 尼恩
|