synchronized的详解、锁的升级过程和优缺点比较
本文 详细介绍Java中为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻量级 锁、重量级锁,以及锁升级过程。
Java中每一个对象都可以作为锁。具体表现形式为以下三种形式:
- 对于普通的同步方法,锁是当前的实例对象
- 对于静态同步方法,锁是当前类的Class对象
- 对于同步方法块,锁是Synchronized括号里面的配置对象
演示:
Person类中的代码:
package com.qcby.demo1;public class Person {public int age;public String name;public int[] arr = {1,2,3};public String[] arr2 = {"aaa","bbb"};public Person son;//锁对象public synchronized void m1(){System.out.println("我是m1开始");try{Thread.sleep(4000);}catch(InterruptedException e){}System.out.println("我是m1结束");}//锁对象public synchronized void m2(){System.out.println("我是m2开始");try{Thread.sleep(4000);}catch(InterruptedException e){}System.out.println("我是m2结束");}//锁类public synchronized static void m3(){System.out.println("我是m3开始");try{Thread.sleep(4000);}catch (InterruptedException e){}System.out.println("我是m3结束");}//锁类public synchronized static void m4(){System.out.println("我是m4开始");try{Thread.sleep(4000);}catch (InterruptedException e){}System.out.println("我是m4结束");}}
Test类中的代码:
package com.qcby.demo1;public class Test {public static void main(String[] args) {System.out.println("阿瑟东");Person ww = new Person();Thread x1 = new Thread() {@Overridepublic void run(){ww.m1();}};Thread x2 = new Thread() {@Overridepublic void run(){ww.m2();}};Thread x3 = new Thread(){@Overridepublic void run(){ww.m3();}};Thread x4 = new Thread(){@Overridepublic void run(){ww.m4();}};x1.start();x2.start();x3.start();x4.start();}}
结果展示:
1、调用同一个对象中的m1、m2方法时:
m2方法会等待m1方法结束之后才能开启
2、调用不同对象中的m1、m2方法时:
m1和m2方法几乎同时开启,同时结束
3、调用同一个类中的m3、m4方法时:
m4方法会等待m3方法结束之后才能开启
锁的升级和对比:
偏向锁
大多数情况下,锁不仅不存在多线程竞争,而且总是由同 一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。
当一个线程访问同步块并且获取锁的时候,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块的时候不需要进行CAS操作来加锁和解锁,只需要测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。
- 如果测试成功,表示线程已经获得了锁
- 如果测试失败,则需 要再测试一下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁)
- 如果没有设置,则 使用CAS竞争锁;
- 如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
偏向锁的撤销:偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时, 持有偏向锁的线程才会释放锁。
轻量级锁
当两个或以上的线程交替获取锁,但并没有在对象上并发的获取锁时,偏向锁升级为轻量级锁。在此阶段,线程采取CAS的自旋方式尝试获取锁,避免阻塞线程造成CPU在用户态和内核态间转换的消耗。
重量级锁
因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级 成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时, 都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮 的夺锁之争。
三种锁的优缺点的对比:
锁 | 优点 | 缺点 | 适用场景 |
偏向锁 | 加锁和不加锁不需要额外的消耗,和事项非同步方法相比只存在纳秒级别的差距。 | 如果线程之间存在锁的竞争,会带来额外的锁撤销的消耗 | 适用于只有一个线程访问同步块场景 |
轻量级锁 | 竞争的线程不会阻塞,提高了程序的响应速度 | 如果一直得不到锁竞争的线程,适用自旋回消耗CPU | 追求响应时间,同步块执行速度非常快 |
重量级锁 | 线程竞争不适用自旋,不会消耗CPU | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步块执行速度比较长 |