首页IT科技sync lock(想会用synchronized锁,先掌握底层核心原理)

sync lock(想会用synchronized锁,先掌握底层核心原理)

时间2025-08-03 20:09:48分类IT科技浏览4926
导读:摘要:synchronized锁修饰方法和代码块时底层实现上是一样的,但是在修饰方法时,不需要JVM编译出的字节码完成加锁操作,而synchronized在修饰代码块时,是通过编译出来的字节码生成的monitorenter和monitorexit指令来实现的。...

摘要:synchronized锁修饰方法和代码块时底层实现上是一样的             ,但是在修饰方法时                       ,不需要JVM编译出的字节码完成加锁操作        ,而synchronized在修饰代码块时          ,是通过编译出来的字节码生成的monitorenter和monitorexit指令来实现的                。

本文分享自华为云社区《Synchronized底层核心原理》                      ,作者: 小威要向诸佬学习呀                      。

synchronized锁用于同步实例方法            ,同步静态方法和同步代码块       。自从Java1.6开始       ,就对synchronized锁进行了很多方面的优化            。对其引入了偏向锁                     ,轻量级锁                ,适应性自旋锁    ,锁粗化                     ,锁消除等各种技术方面的优化                      。

synchronized锁是基于monitor锁实现的                    ,因此在讲解synchronized锁之前,有必要了解一下monitor锁          。

monitor锁的原理

monitor                 ,在中文中有监视器的意思                        ,当创建对象时    ,每一个创建出来的对象都会关联一个monitor对象             ,对于一个java对象                       ,当拿到这个monitor对象时        ,这个monitor对象就会处于锁定的状态          ,其他对象不会再获取                      ,synchronized锁的本质就是基于进入和退出monitor对象实现的同步方法和同步代码块        。

这里首先解释一下wait            ,notify       ,notifyAll等方法的各个作用:

wait方法会让进入object监视器的线程进入到WaitSet集合中等待;notify方法会使在object上正在WaitSet集合上等待的线程中挑一个唤醒线程;notifyAll方法会让正在WaitSet集合中等待的线程全部唤醒                       。

而对于monitor                     ,它是基于ObjectMonitor实现的                ,ObjectMonitor的主要数据结构包括:

owner:owner原本的值为null    ,它用来指向获取到ObjectMonitor对象的线程

             。当一个线程获取到ObjectMonitor对象时                     ,这个ObjectMonitor对象就会存储在当前对象的对象头中的Mark

Word中    。

WaitSet                    ,这个是ObjectMonitor中的一个集合,同时WaitSet与wait()方法有关                        。当Owner线程发现条件不满足时                 ,会调用wait方法                        ,使线程进入WaitSet集合中变为WAITING状态                 。

EntryList    ,也是ObjectMonitor中的一个集合             ,同时EntryList与notify()                       ,notifyAll()方法有关。WAITING状态下的线程会在Owner线程调用notify()或notifyAll()等方法时唤醒        ,但是唤醒之后并不代表着线程会立即拿到锁资源          ,而是需要进入EntryList集合中进行竞争                    。

模拟多线程情况下                      ,同时访问一个被synchronized锁修饰方法时            ,在JVM底层中的流程如下·:

线程进入EntryList集合时       ,如果某个线程获取到monitor对象时                     ,这个线程会进入owner中                ,同时会把monitor对象中的owner变量复制为当前的线程(拿到monitor对象的这个)    ,并且会把monitor对象中的count变量值+1                     。 如果线程调用wait方法                     ,当前的线程就会释放拿到的monitor对象                    ,并且会把monitor对象中的owner变量值设为null,并且count的值-1    。最后                 ,当前线程会进入到WaitSet集合中等待                        ,等候再次被唤醒                。 如果是获得monitor对象的线程执行任务完成后    ,也会进行上面的一系列操作             ,但不会到WaitSet集合中等待了                       ,因为任务已经执行完了                     。

synchronized修饰方法

前面说到synchronized锁是基于monitor锁实现的       。当synchronized锁修饰方法时        ,被此锁修饰的方法会比普通方法的常量池中多一个ACC_SYNCHRONIZED标识符            。当线程调用了被synchronized锁修饰的方法时          ,会检查方法中是否设置了此标识符                      。

如果设置了ACC_SYNCHRONIZED标识符                      ,那么当前的线程会首先获取monitor锁对象            ,然后执行同步代码中的方法       ,完成后会释放monitor对象          。当然                     ,在多线程情况下                ,只有一个线程能够获取此monitor对象    ,并且在该线程释放monitor对象之前                     ,其他线程无法获取此monitor对象        。因此在同一时刻                    ,只能有一个线程拿到相同对象的synchronized锁资源                       。

而当synchronized锁修饰代码块时,与synchronized修饰方法略有不同                 ,接下来详细讲解synchronized修饰代码块的情况             。

synchronized修饰代码块

当synchronized锁修饰代码块时                        ,synchronized关键字会被编译成monitorenter和monitorexit两条指令    ,其中             ,monitorenter会放在代码块的前面                       ,而monitorexit会放在代码块的后面    。

对于monitorenter指令:

每个对象都拥有一个monitor        ,当monitor被占用时          ,就会处于锁定状态                      ,线程执行monitorenter指令时会获取monitor的所有权                        。

当monitor计数为0时            ,说明该monitor还未被锁定       ,此时线程会进入monitor并将monitor的计数器设为1                     ,并且该线程就是monitor的所有者                 。

如果此线程已经获取到了monitor锁                ,再重新进入monitor锁的话    ,那么会将计时器count的值加1。

如果有线程已经占用了monitor锁                     ,此时有其他的线程来获取锁                    ,那么此线程将进入阻塞状态,待monitor的计时器count变为0                 ,这个线程才会获取到monitor锁                    。

对于monitorexit指令:

首先                        ,只有拿到了monitor锁对象的线程才会执行monitorexit指令                     。

其次就是    ,在执行monitorexit指令时             ,计时器count的值会减1                       ,当count的值减到0时        ,当前的线程才会退出monitor          ,此时的线程不再是monitor的所有者                      ,当然执行后            ,其他线程可以获取当前monitor锁的所有权    。

通过对简单代码进行反编译来举例:

执行 javap -c SynchronizedTest.class指令得到以下字节码:

public class Synchronized.SynchronizedTest { public Synchronized.SynchronizedTest(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void synchronize(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #3 // String hello world 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 22 17: astore_2 18: aload_1 19: monitorexit 20: aload_2 21: athrow 22: return Exception table: from to target type 4 14 17 any 17 20 17 any }

由上述编码可以看出       ,在synchronized修饰的代码块中                     ,存在有monitorenter指令和monitorexit指令                。

synchronized锁总结

因此                ,由以上可以得出    ,synchronized锁修饰方法和代码块时底层实现上是一样的                     ,但是在修饰方法时                    ,不需要JVM编译出的字节码完成加锁操作,而synchronized在修饰代码块时                 ,是通过编译出来的字节码生成的monitorenter和monitorexit指令来实现的                     。

点击关注                        ,第一时间了解华为云新鲜技术~

声明:本站所有文章    ,如无特殊说明或标注             ,均为本站原创发布       。任何个人或组织                       ,在未征得本站同意时        ,禁止复制             、盗用                       、采集        、发布本站内容到任何网站          、书籍等各类媒体平台            。如若本站内容侵犯了原著者的合法权益          ,可联系我们进行处理                      。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
os版型是什么意思(新版 OSGi 即将发布) 计算机网络体系结构图解(计算机网络体系结构快速梳理)