首页 > 其他分享 >想会用synchronized锁,先掌握底层核心原理

想会用synchronized锁,先掌握底层核心原理

时间:2022-11-08 11:03:33浏览次数:53  
标签:想会用 monitor synchronized 对象 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底层中的流程如下·:

  1. 线程进入EntryList集合时,如果某个线程获取到monitor对象时,这个线程会进入owner中,同时会把monitor对象中的owner变量复制为当前的线程(拿到monitor对象的这个),并且会把monitor对象中的count变量值+1。
  2. 如果线程调用wait方法,当前的线程就会释放拿到的monitor对象,并且会把monitor对象中的owner变量值设为null,并且count的值-1。最后,当前线程会进入到WaitSet集合中等待,等候再次被唤醒。
  3. 如果是获得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锁的所有权。

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

public class SynchronizedTest {
public void synchronize(){
synchronized (this){
System.out.println("hello world");
}
}
}

执行 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指令来实现的。


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

标签:想会用,monitor,synchronized,对象,monitorexit,线程,修饰,底层
From: https://blog.51cto.com/u_15214399/5832304

相关文章

  • Vue路由实现的底层原理
    在Vue中利用数据劫持defineProperty在原型prototype上初始化了一些getter,分别是router代表当前Router的实例、router代表当前Router的实例、router代表当前Router的实例......
  • 线程安全问题和synchronized关键字
    当多线程对共享变量有读写操作时,可能会产生指令交错,这样就会有线程安全问题,所以产生线程安全问题有两个前提存在在多个线程间共享的变量对共享变量有读写操作,如果都是......
  • synchronized关键字
    Java中的每一个对象都可以作为锁。具体表现为以下3种形式。对于普通同步方法,锁是当前实例对象。对于静态同步方法,锁是当前类的Class对象。对于同步方法块,锁是Synchonized括......
  • 从 unlink/rm 底层实现来看Linux文件系统管理
    文章目录​​1.前言​​​​2.文件系统结构​​​​3.Unlink实现​​文中涉及到的内核源代码版本是3.10.1。1.前言工作中听到一个同事对unlink系统调用的描述,unlink并......
  • 原理解密 → Spring AOP 实现动态数据源(读写分离),底层原理是什么
    开心一刻女孩睡醒玩手机,收到男孩发来一条信息:我要去跟我喜欢的人表白了!女孩的心猛的一痛,回了条信息:去吧,祝你好运!男孩回了句:但是我没有勇气说不来,怕被打!女孩......
  • 18. Spring之IOC底层实现
    一、反射机制1.1、创建JavaBean类packagestar.light.pojo;publicclassUser{privateStringname;privateintage;publicUser(){}pu......
  • synchronized 解决方案
    4.2synchronized解决方案为了避免临界区的竞态条件发生,有多种手段可以达到目的。阻塞式的解决方案:synchronized,Lock非阻塞式的解决方案:原子变量本次课使用阻塞式的......
  • gp map的底层实现原理
    gomap的底层实现是hashtable,根据key查找vlue的时间负责度是O(1)先通过哈希算法得出哈希值对算出来的哈希值进行对槽位总数取模找到对应槽位如果冲突多的话,需要以......
  • ESXi查看底层存储磁盘厂商型号的方式与方法
    ESXi查看底层存储磁盘厂商型号的方式与方法背景公司一台过保的服务器出现了磁盘告警Vendor不太靠谱.过保的机器就不管了不买他们的服务器也不说一下是啥硬盘.想自己......
  • 第四章 SpringBoot 底层机制
    搭建SpringBoot底层机制开发环境1、创建Maven项目lzw-springboot2、导入相关依赖<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.o......