Synchronized关键字详解

synchronized关键字详解

synchronized翻译成中文就是同步的意思,属于重量级锁

在Java早期版本效率十分低下,因为Java线程是映射到操作系统的原生线程之上的,如果要切换线程,都要操作系统的帮忙,而在线程切换的时候,要从用户态切换到内核态,耗时长 如何解决?

在Java6之后,sychronized引入了大量的优化自旋锁,适应性自旋锁,锁消除,锁粗化,偏向锁,轻量级锁来减小锁的开销,

偏向锁增加了JVM的复杂性,也没有为所有应用带来性能提升,在JDK18后就被废弃了

synchronized的使用

主要有三种方法:

1.修饰实例方法

2.修饰静态方法

3.修饰代码块

1.修饰实例方法(锁当前对象实例)

synchronized void method() {
    //业务代码
}

2.修饰静态方法(锁当前类,更具体的是锁定当前类的Class对象,以达到其他线程不能同时访问该类的synchronized修饰的静态方法)

synchronized static void method() {
    //业务代码
}

3.修饰代码块(锁指定的类或者对象)

synchronized(this) {
    //业务代码
}

构造方法能用synchronized修饰?

不能,对象的创建本来就是单线程的

synchronized的底层原理

在解释底层原理之前,我们先了解Java对象头是什么

对象头

如图:

在这里插入图片描述

在JVM中对象的储存一般由三部分组成,对象头,实例变量,填充数据,我们重点了解对象头

在这里插入图片描述在这里插入图片描述

普通数组的对象头占64bits,由Mark Word(32 bits)和Klass Word(32 bits)组成

数组对象的对象头占96bits,由Mark Word(32 bits)和Klass Word(32bits)和array length(32bits)组成

**Klass Word:**简单理解为指针,JVM通过这个指针确定这个对象属于那个类

Mark Word(32bits):结构图如下

在这里插入图片描述

分为无锁(Normal),偏向锁(Biased),轻量级锁(Lightweight Locked),重量级锁(Heavyweight Locked),最后被GC回收

注意参数:

001是无锁,101是偏向锁,00是轻量级锁,10是重量级锁

Monitor

接下来我们讲讲Monitor,Monitor被翻译成监视器,他的底层实现是C++,(主要是实现重量级锁的)他的原理是这样的:

**1.**一开始他是由三个部分组成,两个队列(WaitSet和EntryList),还有一个Owner部分

在这里插入图片描述

**2.**每个对象都关联一个自己的Monitor,当第一个线程进入锁之后,他就会指向Owner,这时候这个Monitor就监视着这个线程(Thread-2)

在这里插入图片描述

**3.**当再来一个线程的时候,他就会看到Monitor的Owner部分已经有了线程,就会排在EntryList队列后面等待,直到Thread-2使用完,他才会指向Owner

在这里插入图片描述

那WaitSet队列呢就是,调用了wait()方法,线程进入等待

但是,因为获取Monitor涉及到了操作系统,加锁就会太消耗性能。

所以轻量级锁出现了

轻量级锁:

他的原理是这样的:

**1.**这里的对象都一样,最主要看Mark word(也就是这个对象的Hashcode Age Bias 01代表无锁),每个线程要获取锁的时候都会创建一个栈帧,包含锁记录结构

在这里插入图片描述

**2.**然后让线程的Object reference指向对象Object,通过cas交换栈帧的Mark Word

在这里插入图片描述

举个例子:(我用了jol的一个工具类来打印出对象头信息)

public class SynchronizedTest {
    public static void main(String[] args) {
        SynchronizedTest obj=new SynchronizedTest();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        synchronized(obj){
            System.out.println("成功获得synchronized锁");
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
    }
}

这里锁住的是实例对象,下图是打印信息:

在这里插入图片描述

这里对应上面代码的第一次打印,二进制结尾一看就是01,说明是无锁状态(non-biasable)

在这里插入图片描述

这个是16进制,转化成二进制是
在这里插入图片描述

结尾是00说明是轻量锁(thin lock),所有当线程进入同步代码块的时候,他是先变成轻量级锁

这里反编译可以看到,进入了一次同步代码块,退出命令有两次,第二次退出保证了即使有异常锁也能被正确释放

在这里插入图片描述

这里可以看到异常的话会跳到25:astore_3,然后接着会释放锁

在这里插入图片描述

但是轻量级锁没有用到monitor,所以没有EntrySet队列来让其他线程等待,第二次线程访问就只能有两种情况:

1.还是这个线程请求拿这个对象的锁

2.其他线程

可重入锁

还是这个线程请求拿这个对象的锁

他的原理是:

当我们再次请求访问这个对象的锁的时候,他的栈帧的锁记录就会是null,表示可重入锁,就不需要交换

在这里插入图片描述

public class SynchronizedReentrantExample {

    public synchronized void outerMethod() {
        System.out.println("Outer method");
        innerMethod(); // 再次进入synchronized方法
    }

    public synchronized void innerMethod() {
        System.out.println("Inner method");
        // 这里可以继续重入其他synchronized方法
    }

    public static void main(String[] args) {
        SynchronizedReentrantExample example = new SynchronizedReentrantExample();
        example.outerMethod();
    }
}

自旋锁

其他线程

原理:

当另一个线程请求这个对象锁的时候,对比锁记录,已经被其他线程拿到了轻量级锁,这时候就会不断重试去获取这个锁,直到前一个线程释放了轻量级锁,然后我才能获取,这就是自旋锁

在这里插入图片描述

自旋锁会智能的调试重试次数,如果一直得不到就会升级成重量级锁(重试多了不如重量级的性能好)

锁膨胀和重量级锁

轻量级锁升级重量级锁原理:

它会为Object对象申请Monitor对象,让Object对象指向Monitor重量级锁的地址,然后它自己在Monitor的EntryList处等待,Monitor的Owner拥有指向前一个线程

在这里插入图片描述

当线程Thread0退出同步块的时候,就会唤醒Thread1来获得锁

偏向锁

偏向锁其实是在轻量级线程之前加的,就是线程创建应该一开始就创建线程,但是太鸡肋,在多线程下,并没有得到实质性的性能提升

原理是:

我们上文就看到Mark Word是由32位字节组成,当一个线程请求对象锁的时候,偏向锁就会得到这个线程的id,然后下次看如果还是这个线程就无需做任何同步操作,失败后就会申请轻量级锁

在这里插入图片描述

锁消除

虚拟机在JIT编译的时候(简单理解为第一次编译的时候),扫描上下文,去除了非共享资源的锁

举个例子就明白了

public class LockEliminationExample {

    private void method() {
        Object lock = new Object();
        synchronized (lock) {
            // 假设这里有复杂的计算或逻辑处理
            System.out.println("在同步块内部");
        }
    }

    public static void main(String[] args) {
        LockEliminationExample example = new LockEliminationExample();
        for (int i = 0; i < 1000; i++) {
            example.method();
        }
    }
}

这个lock对象永远不可能成为共享资源,都是线程私有的

参考:

《深入理解JVM虚拟机》

javaguide:https://javaguide.cn/java/concurrent/java-concurrent-questions-02.html#synchronized-%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E4%BA%86%E8%A7%A3%E5%90%97

黑马程序员JUC视频:https://www.bilibili.com/video/BV16J411h7Rd/?spm_id_from=333.337.search-card.all.click

  • 21
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值