java线程是映射到操作系统原生线程之上的,如果要阻塞或者唤醒一个线程就需要操作系统介入,也就是需要在用户态和内核态之间切换,这种切换会消耗大量的系统资源。在java 1.6之前synchronized 属于重量级锁,效率比较低因为监视器锁(monitor) 依赖于操作系统的Mutex Lock来实现的 挂起线程和恢复线程都需要切换至内核态完成 如果同步代码块中的代码比较简单 这种切换所消耗的时间可能比用户代码执行的时间还长,为减少获得锁和释放锁带来的性能消耗,java1.6之后引入轻量级锁和偏向锁。
偏向锁
在实际应用中,大多数时候锁总是同一个线程持有很少发生竞争,也就是说锁总是被第一个占用它的线程持有,这个线程就是锁的偏向线程,如果在接下来的运行过程中,该锁没有被其他线程访问,那么持有偏向锁的线程将不会触发同步,只需在第一次被拥有的时候记录下偏向线程的id。这样偏向线程就一直持有锁(后续这个线程进入和退出同步代码不需要再次加锁和释放,只需要检查偏向锁的线程id即可)如果自始至终使用锁的线程只有一个偏向锁几乎没有额外开销性能很高。
如果偏向锁的id不同表示发生了竞争,这个时候尝试使用CAS来替换对象头(mark word)里的线程id为新线程id。
- 竞争成功:表示之前的线程已经不存在了(退出了同步代码块) 更新mark Word里的线程id为新线程的id锁不会升级
- 竞争失败:这时可能需要升级为轻量级锁。
偏向锁对象头结构:
代码示例:
public static void main(String[] args) throws Exception{
TimeUnit.SECONDS.sleep(5);
Object o = new Object();
System.out.println("偏向锁");
new Thread(()->{
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}).start();
}
偏向锁撤销:偏向锁只有当其他线程竞争锁时,持有偏向锁的线程才会被撤销,撤销需要等待全局安全点,同时检查持有偏向锁的线程是否还在执行。
轻量级锁
轻量级锁是为了在线程近乎交替执行同步代码块时提高性能。当关闭偏向锁功能或者多线程竞争偏向锁时会导致升级为轻量级锁,就是通过cas(当自旋一定次数还无法获取到锁时再升级为重量级锁)减少使用重量级锁产生的性能消耗。
轻量级锁对象头结构:
代码示例:
public static void main(String[] args) throws Exception{
TimeUnit.SECONDS.sleep(5);
Object o = new Object();
System.out.println("偏向锁");
new Thread(()->{
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}).start();
}
注意:由于偏向锁升级为轻量级锁由虚拟机控制代码不易必现 故关闭-XX:-UseBiasedLocking关闭偏向测试
JVM会为每个线程在当前线程栈帧中创建用于存储锁记录的空间,若一个线程获得锁时发现是轻量级锁,会把锁的MarkWord复制到自己的Displaced Mark Word里面(包含hash值、分代年龄等信息)。然后线程尝试使用CAS将锁的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,说明存在竞争,当前线程就尝试自旋来获取锁。
轻量级锁释放:在释放时,当前线程会通过CAS将Displaced Mark Word的内容复制回MarkWord里,如果没有发生竞争这个复制则会成功。如果有其他线程因为多次自旋导致轻量级锁升级为重量级锁那么CAS会失败,此时会释放锁并唤醒阻塞的线程。
重量级锁
Java中的重量级锁是基于进入和退出Monitor对象实现的,每个对象都会有一个Monitor,Monitor可以和对象一起创建和销毁。Monitor是由ObjectMonitor实现,由C++的ObjectMonitor.hpp文件实现,如下图所示:
重量级锁加锁过程如下图所示:
重量级锁对象头结构:
代码示例:
public static void main(String[] args) {
System.out.println("重量级锁");
Object o = new Object();
for(int i = 0; i < 10; i ++){
new Thread(()->{
synchronized (o){
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
升级为重量级锁后,MarkWord保存了重量级锁的指针,代表重量级锁的ObjectMonitor对象中有字段记录非加锁状态下的MarkWord,锁释放后会将MarkWord信息写会对象头。