synchronized原理

目录

一、基本特点

二、加锁过程 

2.1、偏向锁

2.2、轻量级锁

2.3、重量级锁 

三、其它的优化操作 

3.1、锁消除

3.2、锁粗化 


一、基本特点

synchronized有以下特性:

  1. 开始是乐观锁,如果锁冲突频繁,就转换为悲观锁。
  2. 开始是轻量级锁,如果锁被持有的时间较长,就转换为重量级锁。
  3. 实现轻量级锁的时候大概率用到的自旋锁策略。
  4. 是一种不公平锁。
  5. 是一种可重入锁。
  6. 不是读写锁。 

二、加锁过程 

JVM会将synchronizde锁分为无锁,偏向锁,轻量级锁,重量级锁状态。根据锁的激烈程度,自动进行锁升级。

 

举个栗子:

1、我们在学校每天早上去图书馆学习,‘坤坤’已经在图书馆学习时长两年半,所以坤坤每天都要起一个大早去学习,他真的很努力,所以他到了图书馆基本上没有人,坤坤可以随便坐。

2、过了一个小时,有真爱粉来图书馆找咯咯一起学习,真爱粉就把书本放在桌子上,标志着这个地方有人了,给自己占了个位置,其他人不准用。

3、 随着真爱粉越来越多,学习的人也越来越多,图书馆也就慢慢有了竞争,于是有的同学的书本就会被放在一边,自己的位置被别人占了,真爱粉回来之后发现座位被占了,周围也满了,她就站在原地一直等,相当于自旋状态,可以在第一时间发现空余作为,效率比较高。

4、听说咯咯每天都去图书馆学习,小黑子门也急了,他们也要去图书占位,于是图书馆的座位竞争越来越激烈,高峰时期就要排队(阻塞队列),阻塞队列也就越来越长,每个人都要检查锁,如果没有可用的锁就要去排队,对于synchronized来说,相当于是重量级锁,会调用内核态的加锁指令,来完成获取锁操作。

2.1、偏向锁

第一个尝试加锁的线程,会优先进入偏向锁。

偏向锁不是真的”加锁“,而是给对象头加了一个”偏向锁标记“,记录这个锁属于哪个线程。

如果后续没有其它线程来竞争该锁,那么就不用进行其它同步操作了(避免了加锁解锁的资源开销)如果后续有其它线程来竞争该锁,那就取消原来的偏向锁状态,进入一般的轻量级锁状态。

偏向锁的本质上相当于”延迟加锁“,能不加锁就不加锁,避免资源的浪费。

但是该做标记还是要做,否则无法区分何时真正加锁。

2.2、轻量级锁

随着其它线程进入竞争,偏向锁状态被解除,进入轻量级锁状态

此处的轻量级锁就通过CAS(自旋)来实现.

  • 通过CAS检查并更新一块内存(比如null=>该线程引用)。
  • 如果更新成功,则认为加锁成功。
  • 如果更新失败,则认为锁被占用,继续自旋式等待锁释放(并不放弃CPU)。

自旋操作是让CPU一直空转,比较浪费资源,因此自旋状态不会一直持续进行,而是达到一定时间(重试次数),就不再自旋,也就是”自适应“。

2.3、重量级锁 

如果锁竞争进一步激烈,自旋不能快速获取到锁,就会膨胀为重量级锁。

此处的重量级锁就是指内核提供的mutex.

  • 执行加锁操作,先进入内核状态。
  • 在内核判定当前锁是否已经被占用。
  • 如果该锁没有被占用,则加锁成功,并切换回用户态。
  • 如果该锁被占用,则加锁失败。此时线程进入锁的等待队列,挂起,等待被唤醒。
  • 经历了一系列操作,这个锁被其它线程释放了,操作系统也想起了这个被挂起的线程,于是唤醒这个线程,尝试重新获取锁。 

打印类的布局:


import org.openjdk.jol.info.ClassLayout;

/**
 * 打印类的布局
 */
public class Exe_01 {
    //定义变量
    private int count = 100;
    private long count1 = 200;
    private String hello = "";
    private TextExe_01 textExe_01 = new TextExe_01();

    public static void main(String[] args) throws InterruptedException {
        //创建一个对象的实例
        Object obj = new Object();
        //打印实例布局
        System.out.println("=====任意object对象布局,起初无锁状态");
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        System.out.println("延迟三秒开启偏向锁");
        //延迟3秒开启偏向锁
        Thread.sleep(3000);
        //创建本类的实例
        Exe_01 monitor = new Exe_01();
        //打印实例布局,查看锁状态
        System.out.println("=====打印实例布局,注意查看锁状态为偏向锁");
        System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
        //调用hashCode后,保存hashCode的值
        monitor.hashCode();
        //观察现象
        System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
        System.out.println("==============================");
        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("==== 多个线程参与锁竞争,观察锁状态");
        //强制执行垃圾回收
        System.gc();
        //观察GC计数
        System.out.println("+++++++调用GC后查看age的值");
        System.out.println(ClassLayout.parseInstance(monitor).toPrintable());
        //打印类布局,调用不同的方法查看
        System.out.println("+++++查看类布局");
        System.out.println(ClassLayout.parseClass(Exe_01.class).toPrintable());
        //打印类对象布局
        System.out.println("+++++查看类对象布局");
        System.out.println(ClassLayout.parseInstance(Exe_01.class).toPrintable());
        synchronized (Exe_01.class) {
            //加锁后的类对象
            System.out.println("+++++对类对象加锁后,不同的对象获取锁,观察锁升级为thin lock");
            System.out.println(ClassLayout.parseInstance(Exe_01.class).toPrintable());
        }
        //释放锁之后的类对象
        System.out.println("+++++释放锁后");
        System.out.println(ClassLayout.parseInstance(Exe_01.class).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("===========================================================================================");
        // 调用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(Exe_01.class).toPrintable());
        // 打印类对象布局
        System.out.println("==== 查看类对象布局");
        System.out.println(ClassLayout.parseInstance(Exe_01.class).toPrintable());
    }
}

class TextExe_01 {

}

结果分析: 

里面的参数类型可参考这篇文章-Java对象的内存布局 - JaJian - 博客园 (cnblogs.com) 

 

 

三、其它的优化操作 

3.1、锁消除

synchronized的一种优化策略

synchronized是手动添加的自己获取到锁的处理逻辑,什么时候加,加在哪里,JVM无法去管理,但是在代码编译和运行的时候,JVM可以知道程序是读变量,还是写变量。

如果我们手动对所有的读操作都加了synchronized关键字,但是又没有写操作,,那么这个时候JVM就认为这个锁是多余的,那么synchronized就不会去枷锁了,这种现象就叫做”锁消除“。

注意

多线程状态下,多线程对同一个变量进行修改才会有线程安全问题,而多线程对同一个变量读取没有线程安全问题。

synchronized只有100%确定不需要锁的时候,才会进行锁消除优化 。

3.2、锁粗化 

一段逻辑中如果出现多次加锁解锁,编译器JVM就会自动进行锁粗话。

 真实的代码执行过程,从方法1到方法5全部都执行完,才结束。

 如上面的流程中每一个方法都会有一个申请锁资源,释放锁资源的流程。

那么对于这种现象,synchronized就会认为从头到尾只需要获取一次锁资源,中途不会释放锁。

这个现象就叫“锁粗化”,从方法级别(细粒度变成了业务级别的粗粒度)

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值