多线程Monitor工作原理

🍎1. 什么是Monitor?

我们都知道synchronized的作用是用来保证修饰的代码或者方法执行有且只有一个线程执行,也就是锁。那么在执行被锁住的方式时,synchronized就需要通过monitor来记录和保证锁的状态。所以monitor这里的作用其实就是起到了控制synchronized什么时候获取锁,什么时候释放锁,以及记录了锁被重用的次数。

🍎2. 当多线程时Monitor如何执行

前置知识了解

owner : 指向的是当前获得线程的地址,用来判断当前锁是被哪个线程持有。

waitSet : 是指已经获取得一次锁了,对象调用了wait方法,讲当前线程挂起了就进入了等待队列。等待时间到期的时候唤醒,或者其他线程唤醒。

entryList : 是队列用来获取锁的缓冲区,用来将cxq和waitSet中的数据 移动到entryList进行排队。这个统一获取锁的入口。一般是cxq 或者waitSet数据复制过来进行统一排队。

执行流程

刚开始Monitor中Owner为null
当Thread-2执行synchronized(obj)就会将Monitor的所有者Owner置为Thread-2,Monitor中只能有一个Owner
在Thread-2上锁的过程中,如果Thread-3,Thread-.4,Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED(阻塞)
Thread-2执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争的时是非公平的图中WaitSet中的Thread-0,Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程,

🍎3. 以为字节码举例

对于被synchronized修饰的代码块,在生成class字节码文件中会出现monitorenter、monitorexit。如下面例子所示:

 public void synBlock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter #1
         4: getstatic                 
         7: ldc            
         9: invokevirtual            
        12: aload_1
        13: monitorexit #2
        14: goto          
        17: astore_2
        18: aload_1
        19: monitorexit #3
        20: aload_2
        21: athrow
        22: return

执行monitorenter的线程会尝试获取monitor的所有权,会发生以下三种情况之一:

如果该monitor的计数为0,这线程获得该monitor锁并设置为1
如果当前线程有了这个monitor锁,则该线程的monitor的计数累加1
如果其他线程尝试获取monitor锁,发现monitor的计数不为0,这表示当前线程被其他线程占用,则阻塞,直到这个monitor锁的计数变为0,然后再重新尝试获取。

执行monitorexit的线程就会将montior的计数减1,直到减到0为止,这时候就表示可以释放当前montior的锁了,其他的线程就可以尝试来获取当前代码的锁了。

看到这里,可能会有疑问,为什么生成的字节码文件中,一个monitorenter为什么对存在两个monitorexit,这里其实是考虑到代码发生了异常的情况,当我们在正常执行完任务之后,会执行#2的monitorexit去释放锁,但是出现异常了就会去执行#3的monitorexit的锁。这样就避免了死锁的发生,保证在任何情况下都能正常释放锁。

被修饰的同步方法

同步代码块是使用monitorenter和monitorexit来实现的,对于方法则不是依靠它两来实现的而是通过一个ACC_SYNCHRONIZED的flag修饰符,源代码如下:

 public synchronized void synMethod();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 16: 0

当某个线程需要访问这个方法的时候,会先检查这个方法是否有ACC_SYNCHRONIZED这个标签,如果有就需要先获取monitor锁,其他的方面和同步代码块的逻辑是一样的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Max恒

为了开源加油 ! !

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

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

打赏作者

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

抵扣说明:

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

余额充值