浅谈synchronized

话外:年年问,年年考,但是年年有人不会,不讲废话,有缘的小老弟,看到理解记忆就好了。

 

synchronized是Java中的关键字,是一种同步锁。

1、synchronized作用

  原子性:synchronized保证语句块内操作是原子的
  可见性:synchronized保证可见性(通过“在执行unlock之前,必须先把此变量同步回主内存”实现)
  有序性:synchronized保证有序性(通过“一个变量在同一时刻只允许一条线程对其进行lock操作”)

2、synchronized的使用

  • 修饰实例方法,对当前实例对象加锁
  • 修饰静态方法,多当前类的Class对象加锁
  • 修饰代码块,对synchronized括号内的对象加锁

 

3.synchronized的原理

        jvm基于进入和退出Monitor对象来实现方法同步和代码块同步。
        代码块的同步是利用monitorenter和monitorexit这两个字节码指令。jvm执行到monitorenter指令时,当前线程试图获取monitor对象的所有权,如果未加锁或者已经被当
        前线程所持有,就把锁的计数器+1;当执行monitorexit指令时,锁计数器-1;当锁计数器为0时,该锁就被释放了。如果获取monitor对象失败,该线程则会进入阻塞状态,
        直到其他线程释放锁。
  • synchronized是可重入的,所以不会自己把,自己锁死
  • synchronized锁一旦被一个线程持有,其他试图获取该锁的线程将被阻塞。
 
 
4. synchronized的缺点
          1.效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在试图获得所得线程
          2.不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的
          3.无法知道是否成功获取到锁
 

5.理解Java对象头

在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。
在这里插入图片描述

 

对象头部包括markword和类型指针
 
markword记录了哪些信息?
记录了锁信息,hashCode,以及垃圾回收信息
 
 
6. JVM对synchronized的锁优化
        Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线
        程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统
        Mutex Lock所实现的锁我们称之为“重量级锁”。
        锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。锁可以升级但不能降级。
        偏向锁的获取
            当锁对象第一次被线程获取的时候,虚拟机会把对象头中的标志位设置为“01”,即偏向模式。同时使用CAS操作把获取到这个锁的线程ID记录在对象的Mark Word中,
            如果CAS操作成功。持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。
        偏向锁的释放
            偏向锁使用了遇到竞争才释放锁的机制。偏向锁的撤销需要等待全局安全点,然后它会首先暂停拥有偏向锁的线程,然后判断线程是否还活着,如果线程还活着,则升级
            为轻量级锁,否则,将锁设置为无锁状态。
 
 
       轻量级锁:它不是用来替换重量级锁的,它的本意是在没有多线程竞争的情况下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。
            加锁过程:
            在代码进入同步块的时候,如果此对象没有被锁定(锁标志位为“01”状态),虚拟机首先在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,
            用于存储对象目前Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word)。然后虚拟机使用CAS操作尝试将对象的Mark Word
            更新为指向锁记录(Lock Record)的指针。如果更新成功,那么这个线程就拥有了该对象的锁,并且对象的Mark Word标志位转变为“00”,即表示此对象处于轻
            量级锁定状态;如果更新失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步
            块中执行,否则说明这个锁对象已经被其他线程占有了。如果有两条以上的线程竞争同一个锁,那轻量级锁不再有效,要膨胀为重量级锁,锁标志变为“10”,
            Mark Word中存储的就是指向重量级锁的指针,而后面等待的线程也要进入阻塞状态。
            解锁过程:
            如果对象的Mark Word仍然指向线程的锁记录,那就用CAS操作将对象当前的Mark Word与线程栈帧中的Displaced Mark Word交换回来,如果替换成功,整个同步
            过程就完成了。如果替换失败,说明有其他线程尝试过获取该锁,那就要在释放锁的同时,唤醒被挂起的线程。
            如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁
            比传统重量级锁开销更大。
 
        重量级锁:重量级锁是通过对象内部的一个叫做监视器锁(monitor)来实现的,监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。而操作系统实现线
            程之间的切换需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。

  自旋锁
  互斥同步对性能影响最大的是阻塞的实现,挂起线程和恢复线程的操作都需要转入到内核态中完成,这些操作给系统的并发性能带来很大的压力。
  于是在阻塞之前,我们让线程执行一个忙循环(自旋),看看持有锁的线程是否释放锁,如果很快释放锁,则没有必要进行阻塞。

  锁消除
  锁消除是指虚拟机即时编译器(JIT)在运行时,对一些代码上要求同步,但是检测到不可能发生数据竞争的锁进行消除。

  锁粗化
  如果虚拟机检测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。

 
7. synchronized和lock的区别:
            Lock是一个接口,而synchronized是关键字。
            synchronized会自动释放锁,而Lock必须手动释放锁。
            Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
            通过Lock可以知道线程有没有拿到锁,而synchronized不能。
            Lock能提高多个线程读操作的效率。
            synchronized能锁住类、方法和代码块,而Lock是块范围内的
            注意:
            Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作。
            synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁
 
 
posted @   雪域飞魂  阅读(199)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· winform 绘制太阳,地球,月球 运作规律
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示