《蹲坑也能进大厂》多线程系列-synchronized深入原理终结篇

作者:JavaGieGie

微信公众号:Java开发零到壹

多线程系列目录

《蹲坑也能进大厂》多线程系列文章

前言

上篇在synchronized精讲中,我们讲解了synchronized的用法、注意项等知识点,但知道用法只是基础,如果想要拿到更高的工资、更好的岗位,了解它的实现原理也是很重要的加分项,本章学习的意义非常重要。

本节主要讲解以下几个内容:

  1. synchronized有哪些特性?
  2. synchronized的实现原理?
  3. synchronized的作用?
  4. synchronized的缺陷?

正文

synchronized有哪些性质?

可重入性

定义:可重入是指同一个线程当外层函数获取到锁时,内层函数可以直接再次获取该锁。

看不懂没关系,我们先来看一个例子。

前一篇文章我们说过一妻多夫的生活案例(没有看过的小伙伴建议先看前一篇),小伙伴们想一下,假设兄弟四人中老大先拿到这块砖(锁),那么他当天就有资格进入房间(同步方法/代码块),这没毛病,毕竟早起的鸟儿有虫吃。在老大持有这块砖的过程中,他可以在房间随意走动,他甚至可以去厕所、浴室,卧室等等,并且都不需要再重新去获取那块砖(锁)。

这就叫可重入,一旦线程获取到对象锁(砖),再去访问该对象锁的所有同步方法(浴室、卧室)时,就不用再去重新竞争锁资源。

可能还有聪明的小伙伴觉得不够清晰,这里花Gie用 代码进行演示

//方法一
public synchronized void testSync1(){
    System.out.println("我进来了方法1");
    testSync1();
}

public static void main(String[] args) {
    new SyncDemo().testSync1();
}

运行过这段代码的小伙伴是不是惊喜的发现了什么呢,是不是一直到把IDEA跑崩了,它才结束程序。

image.png

这就可以得出可重入的第一个结论,线程获取到锁后,可以重复进入同一个方法。

那如果是调用不同的synchronized方法呢

public synchronized void testSync1(){
    System.out.println("我进来了方法1");
    testSync2();
}

public synchronized void testSync2(){
    System.out.println("我进来了方法2");
}

结果也很明显,依然可以 调用成功

image.png

从而得到的另一个结论:获取到锁的线程,可以进入同一个对象的不同synchronized方法。

  • 不可中断性

一旦锁被某个线程获取,其他线程再想进入同步方法/代码块,就只能等待,直到持有锁的那个线程释放这个锁,如果一直不释放这个锁,那么其他线程只能一直等待下去。

synchronized释放锁方式:1. 正常结束 ; 2. 抛出异常

这个比较容易理解,如例子中老大获取到锁后,只要他不主动让出那块砖,他就可以一直待在房间中,其他兄弟毫无办法,只能干瞪眼。

好气啊1.gif

synchronized的实现原理?

synchronized并不需要我们手动加锁、释放锁,因此想要分析原理的话,需要反解析class文件进行查看。

反解析操作流程拓展:

  1. 找到java文件路径,使用 javac 文件.java,将java文件编译成class文件;
  2. 使用javap -c -v命令进行反解析,便可以查看到解析后的内容

先看一下第一个例子,使用synchronized修饰代码块

public synchronized void testSync2(){
    synchronized (this){

    }
}

反解析后的内容如下:

image.png

从上面的内容我们可以看到,JVM采用monitorentermonitorexit这两个指令来实现线程同步。

可以把monitorenter理解为 加锁 操作,monitorexit则是 释放锁。每个对象都会维护一个计数器,当一个线程获取到锁(执行monitorenter)后,计数器 加1;释放锁后(执行monitorexit指令),计数器 减1,当计数器的值为 0时,该对象锁被释放,其他等待中的线程开始竞争该锁。

从图中的标记我们可以看到,monitorexit被执行了两次,这是什么原因呢?

因为编译器要保证线程调用完monitorenter指令后,都要执行与其对应的 monitorexit 指令。为了保证在程序发生异常时二者仍然能够保持匹配,编译器会设置一个异常处理器,用于执行异常时的 monitorexit 指令。这样锁也能被正常释放,也不会导致死锁。图中最后一行monitorexit指令,就是异常结束时**,**被用来释放monitor的。

第二个例子,使用synchronized修饰方法

public synchronized void testSync1(){

}

同样反解析后看下是什么内容

image-20210617231148633.png
没想到吧,是不是有那么一瞬间你觉得自己啥都会了,monitorenter指令、monitorexit指令一个都没有

嘲笑.jpg

这是为什么,是不是百思不得其姐,就让花Gie科普一下吧

方法级的同步是隐式的,也就是说不需要通过字节码指令来控制的。JVM通过 ACC_SYNCHRONIZED 访问标志来区分一个方法是否是同步方法。

当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,说明该方法为同步方法,请求的线程需要先获取monitor(监视器锁),然后才能执行方法,在方法执行后再释放monitor。方法执行期间,其他任何线程因为无法获得monitor而处于阻塞状态。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。


synchronized的作用?

分析完原理,现在再来看下synchronized的作用,理解起来就有据可依了,synchronized的作用主要有以下三个:

  1. 原子性:一个操作或者多个操作 要么全部执行完成,要么都不执行。Java内存模型提供了monitorentermonitorexit指令来隐式的实现原子操作,从而实现线程之间互斥的访问同步代码块,将多个小原子操作合成大原子。

  2. 可见性:当多个线程访问同一个变量时,一个线程修改了该变量的值,其他线程能够立即看得到修改后的值。synchronized 解锁之前,必须将工作内存中的数据同步到主内存,因此在解锁后,其它线程操作该变量时每次都可以看到被修改后的值。

  3. 有序性:程序执行的顺序按照代码的先后顺序执行。这其实和 as-if-serial 有关。

    拓展:来自《Java并发编程的艺术》书籍:

    ​ 什么是as-if-serial语义:不管怎么重排序,单线程程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。所以编译器和处理器不会对存在数据依赖关系的操作做重排序,因为这种重排序会改变执行结果。但是,如果操作之间不存在数据依赖关系,这些操作就可能被编译器和处理器重排序。

    double pi  = 3.14;           // A
    double r   = 1.0;            // B
    double area = pi * r * r;    // C
    

    上面3个操作的数据依赖关系如图所示:

    image.png

    A和C之间存在数据依赖关系,同时B和C之间也存在数据依赖关系。因此在最终执行的指令序列中,C不能被重排序到A和B的前面(因为C排到A和B的前面,程序的结果将会被改变)。但A和B之间没有数据依赖关系,编译器和处理器可以重排序A和B之间的执行顺序

    image.png

    as-if-serial语义把单线程程序保护了起来,遵守as-if-serial语义的编译器、runtime和处理器共同为编写单线程程序的程序员创建了一个幻觉:单线程程序是按程序的顺序来执行的

synchronized的缺陷?

  1. 效率低。
    • 等待获取锁的线,不能主动退出等待。
    • 持有锁的线程只能在执行完毕、抛出异常释放锁,除此之外不能主动释放锁。
  2. 无法知道是否成功获取到锁。

总结

本章提到了一些新的概念,比如监视器锁monitorentermonitorexit这类的词语,新入手的小伙伴可能会有一丝丝的难受,但是对于概念性的词汇,我们要试着去理解,了解其作用,不能过于捉急。小伙伴们有什么不理解的可以在下方留言,花Gie肯定有问必答哦,共同进步。

下期预告

本章讲解了synchronized的全部内容,接下来会在 ThreadLocal线程池中进行分享,加油!冲鸭 ~ 希望大家持续关注,为了大厂梦,我们继续肝

点关注,防走丢

以上就是本期全部内容,如有纰漏之处,请留言指教,非常感谢。我是花GieGie ,有问题大家随时留言讨论 ,我们下期见🦮。

文章持续更新,可以微信搜一搜「 Java开发零到壹 」第一时间阅读,后续会持续更新Java面试和各类知识点,有兴趣的小伙伴欢迎关注,一起学习,一起哈🐮🥃。

原创不易,你怎忍心白嫖,如果你觉得这篇文章对你有点用的话,感谢老铁为本文点个赞、评论或转发一下,因为这将是我输出更多优质文章的动力,感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旭仔儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值