sync lock(想会用synchronized锁,先掌握底层核心原理)
摘要: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指令得到以下字节码:
由上述编码可以看出 ,在synchronized修饰的代码块中 ,存在有monitorenter指令和monitorexit指令 。
synchronized锁总结
因此 ,由以上可以得出 ,synchronized锁修饰方法和代码块时底层实现上是一样的 ,但是在修饰方法时 ,不需要JVM编译出的字节码完成加锁操作,而synchronized在修饰代码块时 ,是通过编译出来的字节码生成的monitorenter和monitorexit指令来实现的 。
点击关注 ,第一时间了解华为云新鲜技术~
创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!