synchronized原理
一.锁升级
synchronized相对比较‘智能’,会根据锁竞争的激烈程度进行锁升级,其锁升级策略如下:
我们一一对一下四种上锁情况进行分析:
无锁:锁对象没有锁竞争时,synchronized并不会进行上锁操作,无锁状态是理论上会存在的状态,但是在实际业务场景中几乎不存在
偏向锁:偏向锁并不是真正的加锁,而是对锁对象进行加‘标签’的操作,这个标签记录哪个线程调用了这把锁,如果后续没有其他线程来调用这把锁,就一直保持偏向锁的状态,如果后面有其他线程同样竞争这把锁,那么只需要将标签中的线程与该线程进行对比,如果不一致,则有偏向锁转化为轻量级锁。偏向锁在本质上是延迟加锁的操作:能不加锁就不加锁,尽量避免不必要的加锁开销。
轻量级锁:当有其他线程对这把锁进行锁竞争,偏向锁的锁状态就会被解除,进入轻量级锁,在这里轻量级锁是自适应的自旋锁(通过CAS来实现):通过CAS检查并更新所资源的调用情况,如果更新成功,则加锁成功,更新失败认为锁被占用,不断进行自旋等待(并不放弃cpu),但是不断的自旋是会消耗内存,一般自旋一定的次数和时间就不再自旋了,这就是所谓的‘自适应’。
重量级锁:当锁竞争进一步加强时,轻量级锁就会转化为重量级锁,而重量级锁获取锁的过程如下:首先从用户态进入内核态,判断锁资源是否被占用,没有被占用则进行加锁操作,加锁成功后返回用户态,如果被占用该线程就进入锁的等待队列,当锁资源被释放之后,经过一些列沧海桑田(不及时),这时操作系统才考虑到被挂起的线程,这时才将该线程唤醒,进行加锁操作。
我们举生活中的一个例子对这四种锁状态进行说明:
我们通过代码对四种锁状态进行解释:
import org.openjdk.jol.info.ClassLayout;
public class Demo_404_Layout {
// 定义一些变量
private int count;
private long count1 = 200;
private String hello = "";
// 定义一个对象变量
private TestLayout test001 = new TestLayout();
public static void main(String[] args) throws InterruptedException {
// 创建一个对象的实例
Object obj = new Object();
// 打印实例布局
System.out.println("=== 任意Object对象布局,起初为无锁状态");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
synchronized (obj) {
System.out.println("==== 第一层synchronized加锁后");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
System.out.println("=== 延时4S开启偏向锁");
// 延时4S开启偏向锁
Thread.sleep(5000);
// 创建本类的实例
Demo_404_Layout monitor = new Demo_404_Layout();
// 打印实例布局,注意查看锁状态为偏向锁
System.out.println("=== 打印实例布局,注意查看锁状态为偏向锁");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
System.out.println("==== synchronized加锁");
// 加锁后观察加锁信息
synchronized (monitor) {
System.out.println("==== 第一层synchronized加锁后");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
// 锁重入,查看锁信息
synchronized (monitor) {
System.out.println("==== 第二层synchronized加锁后,锁重入");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
}
// 释放里层的锁
System.out.println("==== 释放内层锁后");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
}
// 释放所有锁之后
System.out.println("==== 释放 所有锁");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
System.out.println("==== 多个线程参与锁竞争,观察锁状态");
Thread thread1 = new Thread(() -> {
synchronized (monitor) {
System.out.println("=== 在线程A 中获取锁,参与锁竞争,当前只有线程A 竞争锁,轻度锁竞争");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
}
});
thread1.start();
// 休眠一会,不与线程A 激烈竞争
Thread.sleep(100);
Thread thread2 = new Thread(() -> {
synchronized (monitor) {
System.out.println("=== 在线程B 中获取锁,与其他线程进行锁竞争");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
}
});
thread2.start();
// 不休眠直接竞争锁,产生激烈竞争
System.out.println("==== 不休眠直接竞争锁,产生激烈竞争");
synchronized (monitor) {
// 加锁后的类对象
System.out.println("==== 与线程B 产生激烈的锁竞争,观察锁状态为fat lock");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
}
// 休眠一会释放锁后
Thread.sleep(100);
System.out.println("==== 释放锁后");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
System.out.println("===========================================================================================");
System.out.println("===========================================================================================");
System.out.println("===========================================================================================");
System.out.println("===========================================================================================");
System.out.println("===========================================================================================");
System.out.println("===========================================================================================");
// 调用hashCode后才保存hashCode的值
monitor.hashCode();
// 调用hashCode后观察现象
System.out.println("==== 调用hashCode后查看hashCode的值");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
// 强制执行垃圾回收
System.gc();
// 观察GC计数
System.out.println("==== 调用GC后查看age的值");
System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
// 打印类布局,注意调用的方法不同
System.out.println("==== 查看类布局");
System.out.println(ClassLayout.parseClass(Demo_404_Layout.class).toPrintable());
// 打印类对象布局
System.out.println("==== 查看类对象布局");
System.out.println(ClassLayout.parseInstance(Demo_404_Layout.class).toPrintable());
}
}
}
二.锁消除
锁消除是synchronized的一种优化策略,是编译器+JVM判断锁是否能够消除,如果能够消除就直接消除了
比如以下场景:当我们对变量进行修改时,将修改的代码块加上了synchronized,但是是在单线程的情况下,不存在线程安全问题,这时JVM直接将锁消除了
需要注意的是:只有JVM百分百确定不存在线程安全问题时,才会执行锁消除
另外一种场景:虽然JVM没办法决定在哪个地方加锁(程序员决定),但是如果是在多线程情况下的读操作,就不存在线程安全问题,所以这JVM直接将锁消除了
三.锁粗化
当在一段代码中出现多次加锁和释放锁的操作,这时候JVM会对锁自动进行锁的粗化,避免了锁的多次获取和释放
还没有评论,来说两句吧...