关于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,一经查实,立即删除!

相关文章

Keepalived详解之 - LVS(IPVS)管理工具ipvsadm使用指南

ipvsadm是什么? ipvsadm是用来配置、维护或者查看Linux内核当中virtual server table的一个工具, LVS(Linux virtual server)能基于一个集群当中的两个或者多个节点来创建弹性网络服务, 集群中的处于激活状态的节点能将服务请求重定向到一组实际提供服务的主机当中, 提供包括T…

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

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

mysql触发器区分新增 修改_MySQL触发器 , 判断更新操作前后数据是否改变

改动表为user , 改动后将部分信息写入cfq_tab表1. 新增触发器create TRIGGER cfq_on_user_addafter insert on userfor each ROWBEGINinsert into cfq_tab(id,type_id,type,status) values (null,new.UID,1,0);end;2. 修改触发器 , 判断修改前后数据是否变动//DROP TRIGGER IF…

三年级计算机课画曲线,三年级上册信息技术曲线工具教案

教材分析《嬉戏山水间—曲线工具》本课教学时间一课时,主要内容是学习在Windows下学会画图软件中“曲线”工具的使用方法。根据教材的编排,本课之前,学生已学会了画图软件中椭圆和直线工具、多边形工具的使用,已经具备了画图软件的…

如何使用不同的记录器实现配置SLF4J

将slf4j库用作Java应用程序日志记录API层有很多好处。 在这里,我将展示一些示例,说明如何与不同的记录器一起使用和配置它。 您可以将slf4j视为Java接口,然后在运行时需要一个实现(仅一个)来提供实际的日志记录详细信…

Presto 学习

Presto 基础知识与概念学习可以参考这些博客: presto 0.166概述 https://www.cnblogs.com/sorco/p/7060166.html Presto学习-presto介绍 https://blog.csdn.net/paicMis/article/details/78516475 Presto必知必会 https://blog.csdn.net/jiangshouzhuang/article/de…

4月24号

今天老师讲了好多理论,首先讲了一个程序运行多次就是多个进程, from multiprocessing import Processimport time import osdef task(): print(%s:父进程%s%(os.getpid(),os.getppid())) time.sleep(3)if __name____main__: pProcess(targettas…

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

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

docker build 变量_DockerFile 设置环境变量

镜像的Layer 在docker docs里面有一句话: We’ve already seen that Docker images are read-only templates from which Docker containers are launched. Each image consists of a series of layers. Docker makes use of union file systems to combine these layers into…

计算机巧用剪纸做画册教案,大班绘画剪纸制作的教案总结

大班绘画剪纸制作的教案总结第一篇:写生画:目标:培养幼儿细微的观察能力,进一步引导幼儿能抓住人物的典型特征,大胆表现。准备:不同脸型人物的范画两张。过程:一、导入。1. 组织游戏…

python之路_day6

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

Netty 简介

上文我们介绍了NIO和BIO的区别&#xff0c;NIO相对于BIO是一次很大的进步。但我们平时开发中并不会使用NIO。这是因为NIO在开发中存在以下问题 NIO的类库和API繁杂&#xff0c;使用麻烦&#xff0c;需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等&am…

简单性与鲁棒性–在锁定文件处理中展示

今天&#xff0c;我们将讨论在设计不足和过度设计之间保持简单&#xff0c;愚蠢&#xff08;KISS&#xff09;和鲁棒性的设计价值之间的冲突。 我们正在编写一个批处理Java应用程序&#xff0c;需要确保在服务器上一次最多运行一个实例。 一个团队成员有一个很好的想法&#x…

mssql导出数据到mysql_MSSQL数据导出到MYSQL

MSSQL数据导出到MYSQL花了一天时间把MSSQL里的数据导出到MYSQL&#xff0c; 好麻烦&#xff0c;二个数据库都是阿里云买的云服务器。先上阿里云控制面板&#xff0c;备份下MSSQL数据库&#xff0c;下载备份下来&#xff0c;在本地电脑上还原2.本地MSSQL数据库上执行select * in…

演示教学法在计算机基础课程中的应用,演示教学法在《计算机基础》课程中的应用...

科 学 论坛演示教学法在《计算机基础》课程中的应用撩淹阳辉(湖南铁路科技职业技术学院 湖南 株洲 412006)摘 要&#xff1a;文章通过谈了笔者自己对《计算机基础》课程中演示教学方法的思考&#xff0c;明确了在《计算机基础》课程中运用演示教学的实效&#xff0c;但同时针对…

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

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

mysql dump 表数据 shell 脚本

userubuntu:~$ cat mysql_jd_espc_dump.sh ## 这段语句为导出语句mysqldump -uroot -ppassword espc --ignore-tableespc.contract --ignore-tableespc.signature --ignore-tableespc.invitation > ~/dump_1.sql## 下面是其他定制业务&#xff0c;不需要关注 sed s/MyISAM/…

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

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

mysql改date格式_mysql数据库修改添加Date格式列的方法

import java.sql.*;import java.text.DateFormat;//数据库的查询public class SelectTable {String dbDriver"com.mysql.jdbc.Driver";String dbUrl"jdbc:mysql://localhost:3306/sss";//根据实际情况变化String username"root";String password…

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

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