synchronized 使用 优化 原理

一、使用:

// 
    // 1 静态方法上加synchronized 锁对象是class文件
    public static synchronized void test1() {
        System.out.println("使用静态方法加锁。。");
    }
    // 2 普通方法上加synchronized 锁对象是this对象(注意如果对象不是单例,加锁没有意义)
    public synchronized void test2() {
        System.out.println("使用普通方法加锁。。");
    }
    // 3 普通方法内加synchronized 锁对象是this对象(注意如果对象不是单例,加锁没有意义)
    public  void test3() {
        synchronized (Object.class) {
            System.out.println("方法内部加锁。。");
        }
    }

二、优化:

2.1:锁粗化:

例如:

    StringBuffer sb = new StringBuffer();
    public void test1() {
        sb.append(1);
        sb.append(2);
        sb.append(3);
        sb.append(4);
    }
    // append 方法源码如下
    public synchronized StringBuffer append(int i) {
        toStringCache = null;
        super.append(i);
        return this;
    }

test1方法 有4次加锁解锁,加锁解锁非常消耗性能,
所以从 java 1.6 以后,上面的test1方法不在是4次加锁解锁,而是优化一次加锁解锁,append方法的锁消除掉,逻辑如下:

public void test1() {
        synchronized (Object.class) {
            sb.append(1); // append方法的synchronized锁消除掉
            sb.append(2);
            sb.append(3);
            sb.append(4);
        }
    }

2.2:锁消除:

例如:

 	 public Integer test() {
        synchronized (new Object()) {
            int a = 1;
            System.out.println("锁消除代码");
            return  a;
        }
    }

锁对象不一致,加锁无意义,所以jvm 会去掉synchronized 锁, java 1.6 以后这个优化叫做锁消除

2.3:锁膨胀升级:

 java 1.6 后 ,做了优化,底层不在直接使用monitor重量级锁。下面讲原理会分析整个过程(这里只是图片,在下面第三块会分析整个流程)。

在这里插入图片描述

三、synchronized原理:

3.1 简介

	synchronized: 内置锁、隐式锁、对象锁,言外之意不需要手动加解锁,使用对象加锁。

3.2 jvm执行synchronized

在这里插入图片描述
解释上述流程图:
synchronized关键字 翻译成命令

public Integer test() {
        synchronized (xxxx) {
            int a = 1;
            System.out.println("代码");
            return  a;
        }
    }

翻译成命令后 变成

public Integer test() {
        monitorEnter
            int a = 1;
            System.out.println("代码");
            return  a;
        monitorExit
    }

当一个线程执行到monitorEnter命令时 , 尝试去抢占对象锁,如果锁已经被占用,那么线程进入等待队列。如果成功获取到锁,那么执行代码逻辑直到执行命令monitorExit,当前线程退出锁,通知其他等待线程竞争锁。

3.2 对象是怎么加锁的呢?

锁状态是被记录在每个对象的对象头(Mark Word)中,下面我们一 起认识一下对象的内存布局:

在这里插入图片描述
整个对象结构分为三个部分:对象头,实例数据,对齐填充位。而我们锁主要是靠对象头mark word部分实现,下图是mark word 结构和定义
在这里插入图片描述
那么下面是讲述锁的原理包含上述没有讲解锁升级的过程):
对象加锁主要是依靠对象头的mark word。下面是锁升级过程mark word数据变化。弄清楚了,也就明白了对象是怎么加锁。

无锁:synchronized加锁的代码还没被线程执行过。 mark word数据如下
在这里插入图片描述

偏向锁:一旦有线程访问执行synchronized加锁的代码,立马升级为偏向锁, mark word:修改为
在这里插入图片描述
偏向锁标志修改为1,最开始25位修改为当前线程id。

偏向锁执行流程:凡是有线程进来,对比mark word 最后两位,01表示无锁或者偏向锁。然后对比倒数第三位,1表示偏向锁,0表示无锁。因此mark word最后三位数组合为 0 01 表示无锁,最开始25位修改为当前线程id,1 01 表示偏向锁,这时候会对比线程id,如果相同那么直接执行同步代码,不相同会尝试去获取一次偏向锁获取到则直接替换threadId,执行同步代码,获取失败则升级为轻量级锁。

轻量级锁:当多个线程执行synchronized加锁的代码,且上一个线程没有执行完毕同步代码块,下一个线程也需要执行同步代码块。这个时候未拿到锁的线程需要等待。这种线程等待时间很短就能获取到锁。通过自旋等待方式。 mark word:修改为
在这里插入图片描述
mark word前30位改成lock record指针,最后两位是锁表示00(轻量级锁)
lock record解释 :这是每个线程的线程栈开辟的一块空间,这个空间是存储了mark word和一些变量(例如:变量:owner 指向mark word指针)把这个线程栈空间叫做lock record

轻量级执行流程:多个线程检查锁表示位00 ,直接去竞争锁,修改lockrecord指针,如果成功,那么直接执行同步代码,失败线程进入自旋等待。如果自旋一定次数为获取到锁,那么就会升级到重量级锁。

重量级锁:当多线程竞争很激烈,自旋等待失败后,升级位重量级锁。,mark word 如下
在这里插入图片描述
前30位指向monitor指针,最后两位表示成重量级锁 10
monitor解释:monitor相当于所对象与之对应的一个对象,每个对象创建的时候都会有一个与之对应的monitor,这个monitor重量级锁底层是mutex实现,是需要内核空间互斥量,涉及到用户态空间和内核态空间转换,非常消耗资源。

重量级执行流程:多个线程检查锁表示位10 ,直接去竞争monitor锁,如果成功,那么执行同步代码,失败当前线程加入到monitor的内部等待队列,等待当前执行完成,唤醒等待线程去竞争锁。

到这里已经把对象怎么加锁的变化过程梳理了一遍,有的地方没有到很细节,下面来个mark word汇总
在这里插入图片描述

问题答疑:

1、 所有new 出来的对象是否都存在堆区?
答案:否,因为如果jvm逃逸分析,一部分对象会存在栈区。

### Java 中 `synchronized` 关键字的底层实现原理 #### 同步代码块的结构 当定义一个同步方法或同步代码块时,实际上是在告诉 JVM 需要对该段代码进行锁定处理。对于给定的例子: ```java private int i = 0; public void fun(){ synchronized(this){ i++; } } ``` 这段代码表示只有获得当前实例 (`this`) 的锁之后才能进入并执行 `{}` 大括号内的语句[^1]。 #### 底层编译后的指令变化 在 JVM 层面,每当遇到 `synchronized` 修饰的方法调用或是显式的 `synchronized` 块时,会转换成特定的操作码来控制锁的行为。具体来说就是通过 `monitorenter` 和 `monitorexit` 操作符来进行加锁和解锁操作。每次进入由 `synchronized` 定义的临界区之前都会先执行一次 `monitorenter` 来尝试获取对象上的监视器(即锁),而一旦退出该区域则自动触发相应的 `monitorexit` 以释放持有的锁资源[^3]。 #### 锁的状态变迁与优化策略 JVM 对于锁有着多种不同的状态管理方式,包括但不限于无锁、偏向锁、轻量级锁以及重量级锁等形式。这些状态之间的转变取决于实际运行环境中的竞争程度等因素。例如,在没有任何其他线程试图访问相同对象的情况下,默认采用的是偏向锁模式;而在多线程并发争抢同一把锁的情形下,则可能升级至更重级别的锁形式如膨胀为重量级锁[^5]。 - **无锁**:最初始状态下,对象头中仅保存一些基本信息而不涉及任何锁机制。 - **偏向锁**:允许单一线程长时间持有某个对象的独占使用权,减少不必要的上下文切换开销。 - **轻量级锁**:适用于短时间内的低冲突场景,利用原子性的 CAS (Compare And Swap) 操作快速完成上锁过程。 - **重量级锁**:当上述几种简化版无法满足需求时才会启用的传统 Monitor 实现方案,通常伴随着较高的性能损耗。 综上所述,`synchronized` 是一种基于 JVM 内部监控器机制构建而成的关键字,它能够有效地保障共享数据的一致性和可见性,尽管相比某些高级别的并发工具类而言可能会带来一定的性能损失,但在大多数情况下仍然是简单易用的选择之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值