Synchronized关键字⭐️从模拟并发场景案例到字节码底层原理的逐步分析

                                                            

    

目录

    前言

 一、线程不安全案例

二、synchronized关键字底层原理

三、Monitor结构

四、原理总结

 拓展

                                                最后


前言

小伙伴们大家好,上次介绍了创建线程的四种方式,可以点击下方链接熟悉下,这次来分析线程使用中要考虑的线程安全问题

创建线程的四种方式

 一、线程不安全案例

        1、经典中的经典,抢票案例,如下图,创建一个SynTest类,定义了ticketNum代表票的数量,一个抢票的方法getTicketmain方法中循环创建了十五个线程模拟十五名用户在抢票,右键运行,看下结果,好家伙,问题大了,前三个线程买完票数量只减少一张,合着你们仨做一张座,后面更甚者,票数为零了还能抢到一张,站票是吧(三哥直呼内行)。开个玩笑,这种情况是肯定不允许的,也就是多线程情况下要保证线程的安全

public class SynTest {
    int ticketNum = 10;

    public void getTicket(){
            if(ticketNum<=0){
                System.out.println("线程"+Thread.currentThread().getName()+"抢票失败,剩余:"+ticketNum);
                return;
            }
            System.out.println("线程"+Thread.currentThread().getName()+"抢到1张,剩余:"+ticketNum);
            ticketNum--;

    }
    public static void main(String[] args) {
        SynTest synTest = new SynTest();
        for(int i = 0;i<15;i++){
            new Thread(()->{
                synTest.getTicket();
            }).start();
        }
    }
}

2、解决方案

        使用synchronized关键字,在 抢票方法中引用了同步锁,这样每个线程在调用方法执行完成前别的贤臣更不能调用,这样就保证了安全有序,结果如下

public class SynTest {
    final static Object lock = new Object();
    int ticketNum = 10;

    public void getTicket(){
        synchronized (lock){
            if(ticketNum<=0){
                System.out.println("线程"+Thread.currentThread().getName()+"抢票失败,剩余:"+ticketNum);
                return;
            }
            System.out.println("线程"+Thread.currentThread().getName()+"抢到1张,剩余:"+ticketNum);
            ticketNum--;
        }
    }

    public static void main(String[] args) {
        SynTest synTest = new SynTest();
        for(int i = 0;i<15;i++){
            new Thread(()->{
                synTest.getTicket();
            }).start();
        }
    }
}

 

二、synchronized关键字底层原理

        那么,到底该关键字底层做了什么,就可以让线程安全有序执行呢,这就要到.class文件中一探究竟了,打开项目包下的编译后的输出目录target文件夹,找到.class所在位置,右键终端打开,然后输入命令“javap -v SynTest.class” 查看字节码文件,如下找到抢票方法的内部:

        注意“monitorenter"字符,是JVM提供的Monitor监视器提供

  • monitorenter  是上锁开始的地方(拆开就是 monitor  enter)
  • monitorexit    解锁的地方(monitor  exit)
  • 这两个代码包住的指令就是上锁的代码
  • 仔细往下看的话,会发现有两个monitorexit指令,防止锁住的代码抛异常后不能及时释放锁

三、Monitor结构

        1.存储结构

  • Owner: 存储当前获取锁的线程的标识,只有一个线程可以获取
  • EntryList: 关联没有抢到锁的线程,也就是处于Blocked状态的线程
  • WaitSet: 关联调用了wait方法的线程,也就是处于waiting状态的线程

        2.具体流程

  •    代码进入synchronized代码块,先对lock(同步锁)关联monitor,然后判断Owner是否有线程持有
  • 若当前没有线程持有,则当前线程持有,标识该线程获取锁成功
  • 如果当前已持有,则让线程进入entryList阻塞,如果持有锁的线程已经释放锁,则在entryList中的线程开始竞争锁的持有权(该过程属于非公平竞争)
  • 如果代码块中调用了wait(),则线程进入waitSet中等待

四、原理总结

        synchronized同步锁采用互斥的方式让同一时刻至多一个线程持有同步锁,底层是由JVM级别的monitor对象实现,线程获得锁需要关联monitor,其中,monitor内部有三个属性,owner:是关联获得锁的线程,entryList:是出于阻塞状态的线程,waitSet:关联的是处于waiting 状态的线程

 拓展

monitor实现的锁属于重量级锁,因为涉及到了用户状态的切换,进程的上下文切换,性能较低

因此在JDK1.6之后引入了偏向锁,轻量级锁这两种新型锁机制,下次来分析分析这两个机制的原理以及使用场景

                                                最后

对于synchronized的使用以及底层原理到这里就结束了,欢迎一起讨论


 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

先锋 Coder

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

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

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

打赏作者

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

抵扣说明:

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

余额充值