当前位置: 首页 > news >正文

Synchronized是怎么实现的?

synchronized是Java中一个很关键的同步实现机制的内置关键字,主要用来加锁,synchonized 所添加的锁有以下几个特点:

  • 互斥性
    • 同一时间点,只有一个线程可以获得锁,获得锁的线程才可以处理被 synchronized 修饰的代码片段。
  • 阻塞性
    • 只有获得锁的线程才可以执行被 synchronized 修饰的代码片段,未获得锁的线程只能阻塞,等待锁释放。
  • 可重入性
    • 如果一个线程已经获得锁,在锁未释放之前,再次请求锁的时候,是必然可以获得锁的

synchronized的用法 

synchronized 的使用方法比较简单,主要可以用来修饰方法和代码块。根据其锁定的对象不同,可以用来定义同步方法和同步代码块。

同步方法

//同步方法,对象锁  
public synchronized void doSth(){System.out.println("Hello World");
}//同步方法,类锁  
public synchronized static void doSth(){System.out.println("Hello World");
}

同步代码块

//同步代码块,类锁
public void doSth1(){synchronized (Demo.class){System.out.println("Hello World");}
}//同步代码块,对象锁
public void doSth1(){synchronized (this){System.out.println("Hello World");}
}

无论是同步方法还是同步代码块,其实现其实都要依赖对象的监视器(Monitor)。那么什么是Monitor呢?

Monitor

为了解决线程安全的问题,Java 提供了同步机制、互斥锁机制,这个机制保证了在同一时刻只有一个线程能访问共享资源。

这个机制的保障来源于监视锁 Monitor,每个对象都拥有自己的监视锁 Monitor。当我们尝试获得对象的锁的时候,其实是对该对象拥有的 Monitor 进行操作。

Monitor 其实是一种同步工具,也可以说是一种同步机制,它通常被描述为一个对象,主要特点是:

对象的所有方法都被“互斥”的执行。好比一个 Monitor 只有一个运行“许可”,任一个线程进入任何一个方法都需要获得这个“许可”,离开时把许可归还。

通常提供 singal 机制:允许正持有“许可”的线程暂时放弃“许可”,等待某个谓词成真(条件变量),而条件成立后,当前进程可以“通知”正在等待这个条件变量的线程,让他可以重新去获得运行许可。

Monitor的代码实现

 在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++ 实现的,由 ObjectMonitor 实现的,其主要数据结构如下:

ObjectMonitor() {_header       = NULL;_count        = 0;_waiters      = 0,_recursions   = 0;_object       = NULL;_owner        = NULL;_WaitSet      = NULL;_WaitSetLock  = 0 ;_Responsible  = NULL ;_succ         = NULL ;_cxq          = NULL ;FreeNext      = NULL ;_EntryList    = NULL ;_SpinFreq     = 0 ;_SpinClock    = 0 ;OwnerIsThread = 0 ;
}

以上的属性我们只需要关注几个比较重要的属性即可

_owner:指向持有 ObjectMonitor 对象的线程

_WaitSet:存放处于 wait 状态的线程队列

_EntryList:存放处于等待锁 block 状态的线程队列

_recursions:锁的重入次数

_count:用来记录该线程获取锁的次数

当多个线程同时访问一段同步代码时,首先会进入 _EntryList 队列中,当某个线程获取到对象的 monitor 后进入 _Owner 区域并把 monitor 中的 _owner 变量设置为当前线程,同时 monitor 中的计数器 _count 加1。即获得对象锁。

若持有 monitor 的线程调用 wait() 方法,将释放当前持有的 monitor,_owner 变量恢复为 null_count 自减 1,同时该线程进入 _WaitSet 集合中等待被唤醒。若当前线程执行完毕也将释放 monitor(锁)并复位变量的值,以便其他线程进入获取 monitor(锁)。如下图所示

下面我们来看一下获取锁的实现

void ATTR ObjectMonitor::enter(TRAPS) {Thread * const Self = THREAD ;void * cur ;//通过CAS尝试把monitor的`_owner`字段设置为当前线程cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;//获取锁失败if (cur == NULL) {         assert (_recursions == 0   , "invariant") ;assert (_owner      == Self, "invariant") ;// CONSIDER: set or assert OwnerIsThread == 1return ;}// 如果旧值和当前线程一样,说明当前线程已经持有锁,此次为重入,_recursions自增,并获得锁。if (cur == Self) { // TODO-FIXME: check for integer overflow!  BUGID 6557169._recursions ++ ;return ;}// 如果当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程if (Self->is_lock_owned ((address)cur)) { assert (_recursions == 0, "internal state error");_recursions = 1 ;// Commute owner from a thread-specific on-stack BasicLockObject address to// a full-fledged "Thread *"._owner = Self ;OwnerIsThread = 1 ;return ;}// 省略部分代码。// 通过自旋执行ObjectMonitor::EnterI方法等待锁的释放for (;;) {jt->set_suspend_equivalent();// cleared by handle_special_suspend_equivalent_condition()// or java_suspend_self()EnterI (THREAD) ;if (!ExitSuspendEquivalent(jt)) break ;//// We have acquired the contended monitor, but while we were// waiting another thread suspended us. We don't want to enter// the monitor while suspended because that would surprise the// thread that suspended us.//_recursions = 0 ;_succ = NULL ;exit (Self) ;jt->java_suspend_self();}
}

释放锁的实现

void ATTR ObjectMonitor::exit(TRAPS) {Thread * Self = THREAD ;//如果当前线程不是Monitor的所有者if (THREAD != _owner) { if (THREAD->is_lock_owned((address) _owner)) { // // Transmute _owner from a BasicLock pointer to a Thread address.// We don't need to hold _mutex for this transition.// Non-null to Non-null is safe as long as all readers can// tolerate either flavor.assert (_recursions == 0, "invariant") ;_owner = THREAD ;_recursions = 0 ;OwnerIsThread = 1 ;} else {// NOTE: we need to handle unbalanced monitor enter/exit// in native code by throwing an exception.// TODO: Throw an IllegalMonitorStateException ?TEVENT (Exit - Throw IMSX) ;assert(false, "Non-balanced monitor enter/exit!");if (false) {THROW(vmSymbols::java_lang_IllegalMonitorStateException());}return;}}// 如果_recursions次数不为0.自减if (_recursions != 0) {_recursions--;        // this is simple recursive enterTEVENT (Inflated exit - recursive) ;return ;}//省略部分代码,根据不同的策略(由QMode指定),从cxq或EntryList中获取头节点,通过ObjectMonitor::ExitEpilog方法唤醒该节点封装的线程,唤醒操作最终由unpark完成。

 

我们知道了 synchronized 对某个对象进行加锁的时候,会调用该对象拥有的 objectMonitor 的 enter 方法,解锁的时候会调用 exit 方法。

事实上,只有在 JDK1.6 之前,synchronized 的实现才会直接调用 ObjectMonitor 的 enter 和 exit ,这种锁被称之为重量级锁。为什么说这种方式操作锁很重呢?

  • Java 的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统的帮忙,这就要从用户态转换到核心态,因此状态转换需要花费很多的处理器时间,对于代码简单的同步块(如被 Synchronized 修饰的 get 或 set 方法)状态转换消耗的时间有可能比用户代码执行的时间还要长,所以说 synchronized 是 java 语言中一个重量级的操纵。

所以,在 JDK1.6 中出现对锁进行了很多的优化,进而出现轻量级锁,偏向锁,锁消除,适应性自旋锁,锁粗化(自旋锁在 1.4 就有 只不过默认的是关闭的,JDK1.6 是默认开启的),这些操作都是为了在线程之间更高效的共享数据 ,解决竞争问题。

因此,我们对synchronized的实现可以总结为以下:

synchronized 是 Java 中的一个很重要的关键字,主要用来加锁。synchronized 的使用方法比较简单,主要可以用来修饰方法和代码块。根据其锁定的对象不同,可以用来定义同步方法和同步代码块。

1.方法级的同步是隐式的(同步方法)。同步方法的常量池中会有一个 ACC_SYNCHRONIZED 标志。当某个线程要访问某个方法的时候,会检查是否有 ACC_SYNCHRONIZED,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

2.同步代码块使用 monitorenter 和 monitorexit 两个指令实现。 可以把执行 monitorenter 指令理解为加锁,执行 monitorexit 理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为 0,当一个线程获得锁(执行 monitorenter )后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行 monitorexit 指令)的时候,计数器再自减。当计数器为 0 的时候。锁将被释放,其他线程便可以获得锁。

 

相关文章:

  • [SwiftUI 开发] @dynamicCallable 与 callAsFunction:将类型实例作为函数调用
  • 力扣9.28
  • Python按照指定“字体大小以及字体格式”,批量更新Word文档内容(10)
  • 基于Java+SQL Server2008开发的(CS界面)个人财物管理系统
  • Python编码系列—Python备忘录模式:掌握对象状态保存与恢复技术
  • HTTP请求中GET与POST方法的核心区别与用途解析
  • VMware下的ubuntu显示文字太小的自适应显示调整
  • 力扣题解2286
  • 【高分系列卫星简介——高分五号卫星(GF-5)】
  • Jenkins入门:从搭建到部署第一个Springboot项目(踩坑记录)
  • 【NodeJS】npm、yarn、pnpm当前项目设置国内镜像源
  • 【算法】分治:归并排序之LCR 170.交易逆序对的总数(hard)
  • linux脚本工具
  • 【Godot4.3】简单物理模拟之圆粒子碰撞检测
  • 【Java】虚拟机(JVM)内存模型全解析
  • php的引用
  • CSS实用技巧
  • JAVA_NIO系列——Channel和Buffer详解
  • maya建模与骨骼动画快速实现人工鱼
  • mysql常用命令汇总
  • MySQL几个简单SQL的优化
  • Python爬虫--- 1.3 BS4库的解析器
  • python学习笔记 - ThreadLocal
  • React Transition Group -- Transition 组件
  • 闭包--闭包之tab栏切换(四)
  • 道格拉斯-普克 抽稀算法 附javascript实现
  • 给github项目添加CI badge
  • 精彩代码 vue.js
  • 移动端 h5开发相关内容总结(三)
  • 应用生命周期终极 DevOps 工具包
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • # 数仓建模:如何构建主题宽表模型?
  • $GOPATH/go.mod exists but should not goland
  • (2)STL算法之元素计数
  • (第二周)效能测试
  • (二)JAVA使用POI操作excel
  • (附源码)springboot家庭装修管理系统 毕业设计 613205
  • (附源码)ssm经济信息门户网站 毕业设计 141634
  • (每日持续更新)jdk api之FileFilter基础、应用、实战
  • .apk文件,IIS不支持下载解决
  • .net wcf memory gates checking failed
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .net6 core Worker Service项目,使用Exchange Web Services (EWS) 分页获取电子邮件收件箱列表,邮件信息字段
  • .net6Api后台+uniapp导出Excel
  • .NetCore+vue3上传图片 Multipart body length limit 16384 exceeded.
  • .sdf和.msp文件读取
  • :=
  • @JsonFormat与@DateTimeFormat注解的使用
  • [ C++ ] STL_vector -- 迭代器失效问题
  • [ CTF ] WriteUp- 2022年第三届“网鼎杯”网络安全大赛(朱雀组)
  • [ CTF ]【天格】战队WriteUp- 2022年第三届“网鼎杯”网络安全大赛(青龙组)
  • [ 云计算 | AWS 实践 ] 基于 Amazon S3 协议搭建个人云存储服务
  • [].slice.call()将类数组转化为真正的数组
  • [120_移动开发Android]008_android开发之Pull操作xml文件