浅析 synchronized 底层

synchronized 是 Java 的关键字之一,提供一种原子性的内部锁,Java 中的每个对象都可以把它当作一个同步锁使用,这种 Java 内置的使用者看不到的锁为内部所,或叫监视器锁

synchronized 内存语义

进入 synchronized块,即是当前线程会从自己的工作内存中清除 synchronized块中使用到的变量,并从主内存中加载。

退出 synchronized块,即是当前线程将对 synchronized块中使用到的变量作出的修改刷新到主存上。

Monitor

简介

即上述所提到的监视器锁中的监视器(monitor),也会被翻译为管程。在操作系统中,面对多线程操作时,通常会用到 semaphore (信号量)和mutex(互斥)这两个重要的同步原语,但直接操作这两者会非常复杂。为了能够更容易编写正确程序,在二者的基础上,提出了monitor

需要注意,操作系统本身不支持monitormonitor的支持属于编程语言范围,例如 C 语言不支持monitor,而 Java 是支持的。对于不同语言,monitor的实现方式也不一样。

monitor的发展史上,有三种不同的模型:

  • Hasen模型
  • Hoare模型
  • MESA模型

后面提到的 monitor均指 Java 中实现的 monitor

Java 则参考了 MESA 模型进行实现,在 JVM 使用的 HostSpot 模拟机中,monitor是由 C++ 来实现的,主要数据结构如下:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //monitor进入数
    _waiters      = 0,
    _recursions   = 0;  //线程的重入次数
    _object       = NULL;
    _owner        = NULL; //标识拥有该monitor的线程
    _WaitSet      = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ; //多线程竞争锁进入时的单项链表
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

使用 synchronized 关键字,通常需要指定一个对象,称作monitor object。HotSpot 会自动创建与该对象关联的monitor对象。

特点

同一时刻,monitor仅支持一个 进程 / 线程进入monitor定义的临界区(达成互斥)。同时,还需要对未能进入临界区的 进程 / 线程 进行管理,例如 阻塞 和 唤醒。monitor作为一个同步工具,能够提供管理 进程 / 线程状态的机制。

如若semaphore (信号量)和mutex(互斥),还需要使用者对上述机制进行实现,而monitor在内部进行了实现,对外提供了更简洁易用的接口。

当线程需要获取锁时,会放入 Entry Set 进行等待。

如果线程得到锁,则称为当前锁的Owner

若获得锁的线程运行时发现需要等待外部条件,则可调用wait方法,进入Wait Set等待。

进入Wait Set的线程会被notify的调用唤醒,重新尝试获取锁变为Owner

还需要留意一点时,线程出入临界区时,会分别执行moniterenter/moniterexist系统调用,同时 Java 线程实际上是对操作系统线程的映射。因此在 Java SE1.6 前,使用synchronized会造成线程状态切换(用户态和内核态),增大运行开销。在 Java SE1.6 后,synchronized得到优化,引入了 偏向锁轻量级锁

对象内存模型中的锁标记

在此,需要了解一下 Java 对象的内存模型,Java 的对象构成有三部分:

  • 对象头:包含 Mark Word(标记)、Class Pointer (类信息)
  • 实例数据
  • 对齐填充

其中对象头的 MarkWord 共 32 bit ,包含了锁的状态信息,可见 Mark Word 中包含了四种锁状态:

  • 无锁
  • 偏向锁
  • 轻量级锁
  • 重量级锁

锁会根据竞争条件的不同进行对应上面四种状态,由上至下进行升级,且不能降级。

![](https://img-
synchronized 是 Java 的关键字之一,提供一种原子性的内部锁,Java 中的每个对象都可以把它当作一个同步锁使用,这种 Java 内置的使用者看不到的锁为内部所,或叫监视器锁

synchronized 内存语义

进入 synchronized块,即是当前线程会从自己的工作内存中清除 synchronized块中使用到的变量,并从主内存中加载。

退出 synchronized块,即是当前线程将对 synchronized块中使用到的变量作出的修改刷新到主存上。

Monitor

简介

即上述所提到的监视器锁中的监视器(monitor),也会被翻译为管程。在操作系统中,面对多线程操作时,通常会用到 semaphore (信号量)和mutex(互斥)这两个重要的同步原语,但直接操作这两者会非常复杂。为了能够更容易编写正确程序,在二者的基础上,提出了monitor

需要注意,操作系统本身不支持monitormonitor的支持属于编程语言范围,例如 C 语言不支持monitor,而 Java 是支持的。对于不同语言,monitor的实现方式也不一样。

monitor的发展史上,有三种不同的模型:

  • Hasen模型
  • Hoare模型
  • MESA模型

后面提到的 monitor均指 Java 中实现的 monitor

Java 则参考了 MESA 模型进行实现,在 JVM 使用的 HostSpot 模拟机中,monitor是由 C++ 来实现的,主要数据结构如下:

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //monitor进入数
    _waiters      = 0,
    _recursions   = 0;  //线程的重入次数
    _object       = NULL;
    _owner        = NULL; //标识拥有该monitor的线程
    _WaitSet      = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ; //多线程竞争锁进入时的单项链表
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

使用 synchronized 关键字,通常需要指定一个对象,称作monitor object。HotSpot 会自动创建与该对象关联的monitor对象。

特点

同一时刻,monitor仅支持一个 进程 / 线程进入monitor定义的临界区(达成互斥)。同时,还需要对未能进入临界区的 进程 / 线程 进行管理,例如 阻塞 和 唤醒。monitor作为一个同步工具,能够提供管理 进程 / 线程状态的机制。

如若semaphore (信号量)和mutex(互斥),还需要使用者对上述机制进行实现,而monitor在内部进行了实现,对外提供了更简洁易用的接口。

当线程需要获取锁时,会放入 Entry Set 进行等待。

如果线程得到锁,则称为当前锁的Owner

若获得锁的线程运行时发现需要等待外部条件,则可调用wait方法,进入Wait Set等待。

进入Wait Set的线程会被notify的调用唤醒,重新尝试获取锁变为Owner

还需要留意一点时,线程出入临界区时,会分别执行moniterenter/moniterexist系统调用,同时 Java 线程实际上是对操作系统线程的映射。因此在 Java SE1.6 前,使用synchronized会造成线程状态切换(用户态和内核态),增大运行开销。在 Java SE1.6 后,synchronized得到优化,引入了 偏向锁轻量级锁

对象内存模型中的锁标记

在此,需要了解一下 Java 对象的内存模型,Java 的对象构成有三部分:

  • 对象头:包含 Mark Word(标记)、Class Pointer (类信息)
  • 实例数据
  • 对齐填充

其中对象头的 MarkWord 共 32 bit ,包含了锁的状态信息,可见 Mark Word 中包含了四种锁状态:

  • 无锁
  • 偏向锁
  • 轻量级锁
  • 重量级锁

锁会根据竞争条件的不同进行对应上面四种状态,由上至下进行升级,且不能降级。

锁升级过程可以参考:https://www.bilibili.com/video/BV1xT4y1A7kA?spm_id_from=333.999.0.0

参考链接

https://blog.csdn.net/qq_36934826/article/details/95978700

https://blog.csdn.net/qq_36934826/article/details/95978700

https://www.cnblogs.com/wade-luffy/p/5969418.htmlblog.csdnimg.cn/img_convert

锁升级过程可以参考:https://www.bilibili.com/video/BV1xT4y1A7kA?spm_id_from=333.999.0.0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`synchronized` 关键字的底层实现和锁的获取与释放过程涉及到了 Java 虚拟机(JVM)、Java 对象头和操作系统等多个层面。 当一个线程访问一个被 `synchronized` 修饰的方法或代码块时,JVM 会首先尝试获取这个同步锁。如果这个锁没有被其他线程占用,则该线程可以顺利获取锁,并进入临界区。如果这个锁已经被其他线程占用,则该线程会进入阻塞状态,直到获取到锁为止。 在 Java 对象头中,有一个字段用于存储这个对象的 Mark Word,这个字段中的一部分用于存储锁的信息,例如锁的状态、持有锁的线程 ID 等。当一个线程获取到锁时,会将这个锁的状态设置为“锁定”,同时将持有锁的线程 ID 记录在 Mark Word 中。当这个线程释放锁时,会将锁的状态设置为“未锁定”,并清除持有锁的线程 ID。 在操作系统层面,锁的获取和释放需要通过操作系统提供的原语来实现,例如 Linux 中的 futex。当线程在获取锁时,如果锁已经被其他线程占用,则它会进入阻塞状态。操作系统会将这个线程加入到等待队列中,并在锁被释放时通知到这个线程,使其重新竞争锁。当线程释放锁时,操作系统会将等待队列中的一个或多个线程唤醒,并将锁的所有权转移到其中一个线程手中。 总的来说,`synchronized` 关键字的实现涉及到了多个层面的细节,包括 Java 虚拟机、Java 对象头和操作系统等。了解其底层实现原理有助于我们更好地理解和使用锁机制,从而编写出更加高效和健壮的多线程程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值