关于Java和Scala同步的五件事你不知道

实际上,所有服务器应用程序都需要在多个线程之间进行某种同步。 大多数同步工作是在框架级别为我们完成的,例如通过我们的Web服务器,数据库客户端或消息传递框架。 Java和Scala提供了许多组件来编写可靠的多线程应用程序。 这些包括对象池,并发集合,高级锁,执行上下文等。 blog_trampoline

为了更好地理解它们,让我们探索最同步的习惯用法-Object lock 。 这种机制为synced关键字提供了动力,使其成为Java中最流行的多线程习惯用法之一(如果不是)。 它也是我们使用的许多更复杂模式的基础,例如线程和连接池,并发集合等等。

synced关键字在两个主要上下文中使用:

  1. 作为方法修饰符,用于标记一种方法,该方法一次只能由一个线程执行。
  2. 通过将代码块声明为关键部分 –在任何给定时间点仅一个线程可以使用一个代码块。

锁定说明

事实1 。 同步代码块使用两个专用字节码指令实现,这是官方规范的一部分-MonitorEnterMonitorExit 。 这与其他锁定机制不同,例如在java.util.concurrent包中找到的那些锁定机制,这些锁定机制是结合Java代码和通过sun.misc.Unsafe进行的本机调用实现的(对于HotSpot而言)。

这些指令对开发人员在同步块的上下文中明确指定的对象进行操作。 对于同步方法,锁定将自动选择为“ this ”变量。 对于静态方法,锁将放置在Class对象上。

同步方法有时会导致不良行为 。 一个示例是在相同对象的不同同步方法之间创建隐式依赖关系,因为它们共享相同的锁。 更糟糕的情况是在基类(甚至可能是第三方类)中声明同步方法,然后将新的同步方法添加到派生类。 这会在整个层次结构中创建隐式同步依赖关系,并有可能导致吞吐量问题甚至死锁。 为避免这些情况,建议使用私有对象作为锁,以防止意外共享或逃脱锁。

编译器和同步

有两个字节码指令负责同步。 这是不寻常的,因为大多数字节码指令彼此独立,通常通过将值放在线程的操作数堆栈上来彼此“通信”。 还可以从操作数堆栈中加载要锁定的对象,该操作数堆栈先前是通过取消引用变量,字段或调用返回对象的方法来放置的。

事实2。 那么,如果在没有分别调用另一条指令的情况下调用了两条指令之一,会发生什么呢? 如果不调用MonitorEnter,Java编译器将不会生成调用MonitorExit的代码。 即使这样,从JVM的角度来看,这样的代码也是完全有效的。 这种情况的结果是MonitorExit指令抛出IllegalMonitorStateException。

如果通过MonitorEnter获得锁但没有通过对MonitorExit的相应调用释放锁,将会发生更危险的情况 。 在这种情况下,拥有该锁的线程可能导致其他试图获取该锁的线程无限期地阻塞。 值得注意的是,由于锁是可重入的,因此拥有该锁的线程可能会继续愉快地执行,即使它再次到达并重新输入相同的锁也是如此。

这就是陷阱。 为了防止这种情况的发生,Java编译器以这种方式生成匹配的输入和退出指令,即一旦执行已进入同步块或方法,它就必须通过匹配的MonitorExit指令来处理同一对象。 可能会引起麻烦的一件事是,如果关键部分抛出异常。

public void hello() {synchronized (this) {System.out.println("Hi!, I'm alone here");}
}

让我们分析一下字节码–

aload_0 //load this into the operand stack
dup //load it again
astore_1 //backup this into an implicit variable stored at register 1
monitorenter //pop the value of this from the stack to enter the monitor//the actual critical section
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Hi!, I'm alone here"
invokevirtual java/io/PrintStream/println(Ljava/lang/String;)Vaload_1 //load the backup of this
monitorexit //pop up the var and exit the monitor
goto 14 // completed - jump to the end// the added catch clause - we got here if an exception was thrown -
aload_1 // load the backup var.
monitorexit //exit the monitor
athrow // rethrow the exception object, loaded into the operand stack
return

编译器用来防止栈不展开而无需通过MonitorExit指令的机制非常简单–编译器添加了一个隐式的try…catch子句以释放锁并重新抛出异常。

事实三 。 另一个问题是在相应的enter和exit调用之间存储的对锁定对象的引用在哪里。 请记住,多个线程可能会使用不同的锁定对象同时执行同一同步块。 如果锁定的对象是调用方法的结果,则JVM极不可能再次执行它,因为它可能会更改对象的状态,甚至可能不会返回相同的对象。 对于自输入监视器以来可能已更改的变量或字段,情况也是如此。

监视变量 。 为了解决这个问题,编译器将一个隐式局部变量添加到方法中,以保存锁定对象的值。 这是一个聪明的解决方案,因为与使用并发堆结构将锁定对象映射到线程(该结构本身可能需要同步)相比,该方法在维护对锁定对象的引用上的开销非常小。 在构建Takipi的堆栈分析算法时,我首先观察到了这个新变量,并发现代码中弹出了意外变量。

注意,所有这些工作都是在Java编译器级别完成的。 JVM非常乐意通过MonitorEnter指令进入关键部分而不退出(反之亦然),或将不同的对象用作对应的enter和exit方法。

锁定在JVM级别

现在让我们更深入地研究如何在JVM级别上实际实现锁。 为此,我们将研究HotSpot SE 7的实现,因为它是VM特定的。 由于锁定可能会对代码吞吐量产生一些不利影响,因此JVM进行了一些非常强大的优化,以使获取和释放锁定的效率尽可能高。

事实#4。 JVM所采用的最强大的机制之一是线程锁偏置 。 锁定是每个Java对象都具有的一种固有功能,就像具有系统哈希码或对其定义类的引用一样。 无论对象的类型如何,都是如此(如果需要,您甚至可以使用基本数组作为锁)。

这些类型的数据存储在每个对象的标头(也称为对象的标记)中。 保留在对象标题中的某些数据保留用于描述对象的锁定状态。 这包括描述对象的锁定状态(即锁定/解锁)的位标志,以及对当前拥有该锁的线程的引用-指向该对象的线程有偏。

为了节省对象标头中的空间,为了减少地址大小并节省每个对象标头中的位(64位和32位JVM为54位或23位),在VM堆的较低段中分配了Java线程对象。分别)。

对于64位–

blog_normal-object

锁定算法

当JVM尝试获取对象的锁时,它会经历从乐观到悲观的一系列步骤。

事实五。 如果线程成功将其自身确立为对象锁的所有者,则该线程将获取该锁。 这取决于线程是否能够在对象的头中安装对自身的引用(指向内部JavaThread对象的指针)。

获取锁。 使用简单的比较交换(CAS)操作即可完成此操作。 这非常有效,因为它通常可以转换为直接CPU指令(例如cmpxchg)。 CAS操作与OS特定的线程驻留例程一起用作对象同步习惯用法的构建块。

如果该锁是免费的,或者先前已对该线程进行了预紧,则该线程将获得对象的锁,并且可以立即继续执行。 如果CAS失败,则JVM将执行一轮自旋锁定,在该循环中线程停放以有效地使其在重试CAS之间进入睡眠状态。 如果这些初始尝试失败(向锁发出更高级别的争用信号),线程将自身进入阻塞状态,并将自己排入争用该锁的线程列表,并开始一系列自旋锁。

在每轮旋转之后,线程将检查JVM全局状态的变化,例如“停止世界” GC的出现,在这种情况下,线程将需要暂停自身直到GC完成以防止出现这种情况。在执行STW GC时获得锁并继续执行的位置。

释放锁。 当通过MonitorExit指令退出关键部分时,所有者线程将尝试查看它是否可以唤醒任何正在等待释放锁的驻留线程。 此过程称为选择“继承人”。 这是为了增加活动性,并防止在释放锁的情况下仍保持线程驻留(也称为绞合)的情况。

调试服务器多线程问题很困难,因为它们往往取决于非常特定的时间安排和操作系统启发。 这就是让我们首先致力于Takipi的原因之一。

参考:我们的JCG合作伙伴 Tal Weiss在Takipi博客上对Java和Scala中的同步不了解的5件事 。

翻译自: https://www.javacodegeeks.com/2013/08/5-things-you-didnt-know-about-synchronization-in-java-and-scala.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/367219.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

精读《你不知道的javascript》中卷

前言 《你不知道的 javascript》是一个前端学习必读的系列,让不求甚解的JavaScript开发者迎难而上,深入语言内部,弄清楚JavaScript每一个零部件的用途。本书《你不知道的javascript》中卷介绍了该系列的两个主题:“类型和语法”以…

撸个微信小程序的省市区选择器

起因 微信小程序虽然已经有现成的封装好的省市区选择器给开发者使用,然鹅不幸的是,微信地址库的数据和公司用的地址库数据很难一一对上,那就只能撸起袖子自己写个组件了。 最终效果 思维导图 主要代码 组件 region-picker.js /* region-pic…

python之路_day6

本节内容&#xff1a;面向对象编程介绍为什么要用面向对象进行开发&#xff1f;面向对象的特性&#xff1a;封装、继承、多态类、方法、引子 你现在是一家游戏公司的开发人员&#xff0c;现在需要你开发一款叫做<人狗大战>的游戏&#xff0c;你就思考呀&#xff0c;人狗作…

MUI调用原生自定义方法实现计算缓存与清空缓存

由于项目需要最近在做webapp开发用的是MUI框架&#xff0c;自己本来是做原生开发的&#xff0c;在开发的时候有一个需求是实现计算缓存和清除缓存的功能&#xff0c;原生java方法实现轻轻松松&#xff0c;网上代码一大把&#xff0c;不过是webapp倒是不好搞&#xff0c;MUI自己…

Java中的多重继承与组合vs继承

有时我写了几篇有关Java 继承 &#xff0c; 接口和组成的文章。 在这篇文章中&#xff0c;我们将研究多重继承&#xff0c;然后学习组成优于继承的好处。 Java中的多重继承 多重继承是创建具有多个超类的单个类的能力。 与其他一些流行的面向对象的编程语言&#xff08;例如C …

开源|蚂蚁金服开源AntV F2:一个专注于移动,开箱即用的可视

小蚂蚁说&#xff1a;AntV 是蚂蚁金服全新一代数据可视化解决方案&#xff0c;主要子产品包括 G2、G6、F2。此前我们已经相继发布过AntV的相关开源消息与版本迭代&#xff0c;包括《蚂蚁金服开源&#xff1a;数据驱动的高交互可视化图形语法G2》&#xff0c;《开源 | 蚂蚁金服开…

Puppeteer入门初探

本文来自网易云社区作者&#xff1a;唐钊最近在看 node 爬虫相关的一些东西&#xff0c;我记得还是很久以前常用的 node 爬虫工具还是 superagengtcherrio,他们的思路是通过发起 http 请求然后截取 respone 的内容&#xff0c;但是随着前端mvvm等框架的盛行&#xff0c;现在更多…

访问量大如何增加服务器,服务器流量过大原因及解决方法

造成网站服务器流量过大的原因&#xff1a;1.网站规模较大(比如门户网站、网络商城等)&#xff0c;即网站本身访问量需求大&#xff0c;查看网站的Page View值、Hits值、日流量都很高。2.网站页面设计不合理&#xff0c;页面中包含大图片或音频、视频文件等文件&#xff0c;导致…

关于VUE项目地图开发中大量点标记绘制一些总结

问题说明 在地图开发中&#xff0c;当地图中绘制大量的标记点后&#xff0c;无论是拖动或者缩放&#xff0c;都会感觉到明显的卡顿现象。&#xff08;一般超过800个点后就比较明显了&#xff09;.在平时的工作业务中&#xff0c;由于公司的实时监控页面需要展现5000-20000车辆…

如何使用Java 5 Executor框架创建线程池

Java 5以Executor框架的形式在Java中引入了线程池&#xff0c;它允许Java程序员将任务提交与任务执行分离。 如果要使用Java进行服务器端编程&#xff0c;则线程池是维护系统可伸缩性&#xff0c;鲁棒性和稳定性的重要概念。 对于那些不熟悉Java中的线程池或这里的线程池的概念…

使用NetBeans 7.4 beta提示进行更好的基于JUnit的单元测试

在上一篇文章中 &#xff0c;我写了NetBeans 7.4 beta中提供的提示 &#xff0c;这些提示提高了开发人员避免Java异常处理带来的讨厌的运行时问题的能力。 在本文中&#xff0c;我将研究如何使用NetBeans 7.4 beta提供的另外两个提示使单元测试在执行单元测试期间更加正确和清晰…

Web 开发中 Blob 与 FileAPI 使用简述

本文节选自 Awesome CheatSheet/DOM CheatSheet&#xff0c;主要是对 DOM 操作中常见的 Blob、File API 相关概念进行简要描述。 Web 开发中 Blob 与 FileAPI 使用简述 Blob 是 JavaScript 中的对象&#xff0c;表示不可变的类文件对象&#xff0c;里面可以存储大量的二进制编…

服务器e系列和l的区别,i.e.和 e.g.的区别和使用方法

举例说明在很多文章中都有使用过&#xff0c;我想这个对大家应该并不陌生&#xff0c;但是大家知道ie和eg的区别吗&#xff0c;他们两个都是举例子的缩写词&#xff0c;但是他们之间的区别大家知道吗&#xff0c;今天我们就来介绍下这两个举例说明的缩写词到底有什么不一样。一…

通过基于JDBC的用户存储部署Identity Server

在这篇文章中&#xff0c;我将演示如何使用JDBC用户存储配置WSO2 Identity Server。 为了演示&#xff0c;我使用的是MySQL用户存储&#xff0c;但是相同的过程也适用于任何其他JDBC用户存储。 我的环境是 操作系统– Ubuntu 12.10 Java – 1.6 WSO2是4.5.0 设置MySQL数据库…

前端路由实现原理(history)

前端路由实现&#xff08;history&#xff09; 了解&#xff1a; HTML5 history新增了两个API:history.pushState和history.replaceState 两个api都接受三个参数 状态对象&#xff08;state object&#xff09;&#xff1a;一个JavaScript对象&#xff0c;与用pushState()方法…

unity 删除服务器项目,在吗?有个支持批量构建项目的好东西推荐给你

Unity Build Server是一种全新的项目构建辅助工具&#xff0c;它可以指定硬件设备&#xff0c;专门用于构建项目版本&#xff0c;帮助工作室大规模构建项目&#xff0c;提高团队生产力。很多人在选择Unity时并不会首先考虑到项目构建问题&#xff0c;而随着项目变得更大、更复杂…

使用WSO2 ESB构建制造服务总线(MSB)

在开始讨论本主题之前&#xff0c;我想介绍一些制造业中常用的术语。 术语制造执行系统&#xff08;MES&#xff09;由AMR Research于1990年提出&#xff0c;从先进的制造计算机信息系统的发展&#xff0c;MES概念已经发展了近三十年。 以下是制造执行系统协会&#xff08;MES…

mysql jion 实现原理_MySQL-join的实现原理、优化及NLJ算法

案例分析&#xff1a;selectc.*fromhotel_info_original cleft joinhotel_info_collection honc.hotel_typeh.hotel_typeandc.hotel_idh.hotel_idwhereh.hotel_idis null这个sql是用来查询出 c 表中有 h 表中无的记录&#xff0c;所以想到了用 left join 的特性(返回左边全部记…

python笔记30-docstring注释添加变量

前言 python里面添加字符串注释非常简单&#xff0c;如何将变量放入 python 的函数注释里面呢&#xff1f; docstring也就是给代码加注释的内容了&#xff0c;python可以给函数&#xff0c;类、方法&#xff0c;模块添加注释内容&#xff0c;注释标准格式一般是三个双引号&…

无线路由器在手机上如何连接服务器,192.168.10.1路由器手机怎么设置? | 192路由网...

问&#xff1a;192.168.10.1路由器手机怎么设置&#xff1f;答&#xff1a;192.168.10.1是一个C类的私有IP地址&#xff0c;目前国产的路由器中&#xff0c;睿因路由器使用192.168.10.1作为默认登录地址。鉴于此&#xff0c;下面鸿哥使用睿因路由器来进行演示介绍。温馨提示&am…