- 浏览: 47425 次
- 性别:
- 来自: 北京
文章分类
最新评论
原文:Things You Didn’t Know About Synchronization in Java and Scala
在实际应用中所有的服务端程序都需要在多线程之间进行某种同步。大多数同步已经有框架完成了,比如我们的web服务器,DB客户端和消息框架。Java和Scala提供了大量的组件用来实现稳定的多线程程序。包括对象池,并发集合,高级锁,执行上下文等。
为了更好的理解这些组件,我们深入了解一下最常用的同步原语——对象所。这个是用synchronized 关键字来实现的,在Java中它是非常流行的多线程原语。这也是其他更复杂模式的基础,比如线程池和连接池,并发集合等。
Synchronized 关键字主要用在以下两个场景:
作为方法的修饰,此方法在同一个时间只能被一个线程执行。
把一个代码块声明为临界区,任何时间只有一个线程能访问。
锁指令
同步代码块有两个专门的字节码指令,MonitorEnter 和 MoniterExit。这个不同于其它锁机制,比如java.util.concurrent包是有Java代码和本地调用来是实现的。
这些作用在对象上的指令是有开发人员在同步块中显示说明的。对于同步方法,锁被加到了“this”对象上。对于静态方法,锁被加到了类对象上。
同步方法有时候会引起坏的结果。其实一个例子是在不同的同步方法间会引起隐式依赖,因为它们共享同一把锁 。更坏的场景是在基类里(可能是一个第三方库)申明了一个同步方法,在子类里又增加了新的同步方法。这造成了不同层次之间的隐式同步依赖,会降低吞吐率甚至是死循环。这时应该使用私有的锁对象来防止偶然的共享,或者不使用锁。
编译器和同步
有两个字节码的指令用来实现同步原语。这并不常见,因为大多数字节码指令是互相独立的,通常通过把值放在线程的操作数堆栈上来互相“通信”。要加锁的对象也是从操作数堆栈装载的,通过引用变量或者方法返回对象把他们放在操作堆栈上。
如果只有其中一个指令被调用了,而另外一个没被调用,会发生什么?Java编译器不会产生只调用MonitorExit而没有调用MonitorEnter的代码。即使Java编译产生了这样的代码 ,在JVM看来这个代码也是非法的。这会让MonitorExit指令抛出一个IllegalMonitorStateException 异常。
一个更危险的例子是MonitorEnter加了锁,但却没有被对应的MonitorExit释放。这种情况下线程一直拥有锁,从而导致其他想获取这把锁的线程一直被阻塞。
为了防止发生一直被阻塞,Java编译器会以以下方式产生代码:一旦进入同步块或者方法,一定能执行到MonitorExit。在临界区抛出异常可以导致这个问题。
编译器使用的机制非常简单,当异常发生时如果没有经过MonitorExit,就增加catch语句来释放锁。
另一个问题是在enter和exit之间的锁对象存储在何处。注意多个线程可以同时执行同步块,使用不同的锁对象。如果锁对象是方法调用的结果,那么JVM极有可能会再次执行它,因为它可能会改变对象状态,或者不会返回同一个对象。如果是同一个对象,那么在monitor执行前这个变量和域已经被改变。
监视变量。为了计算,编译器为方法增加了一个隐式的变量,用来存储锁状态。这个是一个聪明的解决方案,因为它只增加了很小的开销就维护了锁对象,而不是使用并发栈把锁对象隐射到线程(这个结构需要同步)。我是在编译Takipi栈分析算法时发现这个新变量的。
注意这所有的工作都是有Java的编译器完成的。JVM可以非常完美处理只调用MonitorEnter来进入临界区而不用退出(或者相反),或者为方法使用不同的对象。
JVMLevel的锁
让我们更深入的看一下锁在JVM里是怎么实现的。为此我们将会查看HotSpot SE7的实现,因为这个实现每个VM都可能不一样。 因为加锁会影响代码的吞吐率,JVM加入了很大的优化来提高加锁解锁的效率。
其中一个强大机制是线程锁偏向。锁特性是每个Java对象都具有的,很想系统的hashcode或者定义类的引用。不管类的类型是什么,这个都成立(甚至你可以使用一个原始数组作为锁)。
这些类型的数据都存在对象的头部(也被称为对象的标记)。这些数据中的一部分用来描述对象锁的状态。这包括描述对象的锁状态(加锁/没加锁)的比特标志位,一个指向现在拥有锁的线程。
为了节省对象头部的空间,Java的线程对象会被分配在VM栈的低位,这可以减少地址长度从而节约对象头部的比特数(64位的只需要54位,32位的只需要23位)。
64位的比特位分配情况:
锁算法
当JVM尝试去获取一个对象的锁时,会采用从乐观到悲观的步骤来获取。
当线程成为对象锁的拥有者时,这就算加锁成功了。线程是否把指向自己的引用存入对象的头部决定着加锁是否成功。
获取锁的步骤。第一步是使用CAS操作来尝试获取锁。这个操作非常高效,因为常常有与之对应的CPU指令(比如 cmpxchg)。 CAS操作和OS线程停止程序作为对象的同步原语。
如果锁是空闲或者说这个所可以被这个线程优先获取,那么线程就立即获取了这个锁。如果CAS失败了,那么JVM先会自旋一轮,然后线程会睡眠,直到下次CAS。如果这些最初的尝试失败了,线程会把自己放入阻塞状态,并进入竞争锁的列表,开始一系列的自旋。
释放锁。通过执行MonitorExit指令来退出临界区,锁的拥有者会尝试着检查是否可以唤醒正在等待这把锁的线程 。这个过程被称为选择一个继承者。这可以增加活跃度,阻止出现当锁释放后仍有线程在等待这把锁。
调试服务端多线程问题有点难度,因为它们非常依赖调试时机和OS的特性。这也是让我们实现TAkipi的原因之一。
延伸阅读
1.如果你对JVM的锁是如何的实现很感兴趣,可以查看这个源代码 http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/42ca4002efc2/src/share/vm/runtime/synchronizer.cpp
2. 所有关于Java同步API的介绍和技术 http://docs.oracle.com/javase/tutorial/essential/concurrency/index.html
3. Scala的并发 http://twitter.github.io/scala_school/concurrency.html
(全文完)如果您喜欢此文请点赞,分享,评论。
在实际应用中所有的服务端程序都需要在多线程之间进行某种同步。大多数同步已经有框架完成了,比如我们的web服务器,DB客户端和消息框架。Java和Scala提供了大量的组件用来实现稳定的多线程程序。包括对象池,并发集合,高级锁,执行上下文等。
为了更好的理解这些组件,我们深入了解一下最常用的同步原语——对象所。这个是用synchronized 关键字来实现的,在Java中它是非常流行的多线程原语。这也是其他更复杂模式的基础,比如线程池和连接池,并发集合等。
Synchronized 关键字主要用在以下两个场景:
作为方法的修饰,此方法在同一个时间只能被一个线程执行。
把一个代码块声明为临界区,任何时间只有一个线程能访问。
锁指令
同步代码块有两个专门的字节码指令,MonitorEnter 和 MoniterExit。这个不同于其它锁机制,比如java.util.concurrent包是有Java代码和本地调用来是实现的。
这些作用在对象上的指令是有开发人员在同步块中显示说明的。对于同步方法,锁被加到了“this”对象上。对于静态方法,锁被加到了类对象上。
同步方法有时候会引起坏的结果。其实一个例子是在不同的同步方法间会引起隐式依赖,因为它们共享同一把锁 。更坏的场景是在基类里(可能是一个第三方库)申明了一个同步方法,在子类里又增加了新的同步方法。这造成了不同层次之间的隐式同步依赖,会降低吞吐率甚至是死循环。这时应该使用私有的锁对象来防止偶然的共享,或者不使用锁。
编译器和同步
有两个字节码的指令用来实现同步原语。这并不常见,因为大多数字节码指令是互相独立的,通常通过把值放在线程的操作数堆栈上来互相“通信”。要加锁的对象也是从操作数堆栈装载的,通过引用变量或者方法返回对象把他们放在操作堆栈上。
如果只有其中一个指令被调用了,而另外一个没被调用,会发生什么?Java编译器不会产生只调用MonitorExit而没有调用MonitorEnter的代码。即使Java编译产生了这样的代码 ,在JVM看来这个代码也是非法的。这会让MonitorExit指令抛出一个IllegalMonitorStateException 异常。
一个更危险的例子是MonitorEnter加了锁,但却没有被对应的MonitorExit释放。这种情况下线程一直拥有锁,从而导致其他想获取这把锁的线程一直被阻塞。
为了防止发生一直被阻塞,Java编译器会以以下方式产生代码:一旦进入同步块或者方法,一定能执行到MonitorExit。在临界区抛出异常可以导致这个问题。
编译器使用的机制非常简单,当异常发生时如果没有经过MonitorExit,就增加catch语句来释放锁。
另一个问题是在enter和exit之间的锁对象存储在何处。注意多个线程可以同时执行同步块,使用不同的锁对象。如果锁对象是方法调用的结果,那么JVM极有可能会再次执行它,因为它可能会改变对象状态,或者不会返回同一个对象。如果是同一个对象,那么在monitor执行前这个变量和域已经被改变。
监视变量。为了计算,编译器为方法增加了一个隐式的变量,用来存储锁状态。这个是一个聪明的解决方案,因为它只增加了很小的开销就维护了锁对象,而不是使用并发栈把锁对象隐射到线程(这个结构需要同步)。我是在编译Takipi栈分析算法时发现这个新变量的。
注意这所有的工作都是有Java的编译器完成的。JVM可以非常完美处理只调用MonitorEnter来进入临界区而不用退出(或者相反),或者为方法使用不同的对象。
JVMLevel的锁
让我们更深入的看一下锁在JVM里是怎么实现的。为此我们将会查看HotSpot SE7的实现,因为这个实现每个VM都可能不一样。 因为加锁会影响代码的吞吐率,JVM加入了很大的优化来提高加锁解锁的效率。
其中一个强大机制是线程锁偏向。锁特性是每个Java对象都具有的,很想系统的hashcode或者定义类的引用。不管类的类型是什么,这个都成立(甚至你可以使用一个原始数组作为锁)。
这些类型的数据都存在对象的头部(也被称为对象的标记)。这些数据中的一部分用来描述对象锁的状态。这包括描述对象的锁状态(加锁/没加锁)的比特标志位,一个指向现在拥有锁的线程。
为了节省对象头部的空间,Java的线程对象会被分配在VM栈的低位,这可以减少地址长度从而节约对象头部的比特数(64位的只需要54位,32位的只需要23位)。
64位的比特位分配情况:
锁算法
当JVM尝试去获取一个对象的锁时,会采用从乐观到悲观的步骤来获取。
当线程成为对象锁的拥有者时,这就算加锁成功了。线程是否把指向自己的引用存入对象的头部决定着加锁是否成功。
获取锁的步骤。第一步是使用CAS操作来尝试获取锁。这个操作非常高效,因为常常有与之对应的CPU指令(比如 cmpxchg)。 CAS操作和OS线程停止程序作为对象的同步原语。
如果锁是空闲或者说这个所可以被这个线程优先获取,那么线程就立即获取了这个锁。如果CAS失败了,那么JVM先会自旋一轮,然后线程会睡眠,直到下次CAS。如果这些最初的尝试失败了,线程会把自己放入阻塞状态,并进入竞争锁的列表,开始一系列的自旋。
释放锁。通过执行MonitorExit指令来退出临界区,锁的拥有者会尝试着检查是否可以唤醒正在等待这把锁的线程 。这个过程被称为选择一个继承者。这可以增加活跃度,阻止出现当锁释放后仍有线程在等待这把锁。
调试服务端多线程问题有点难度,因为它们非常依赖调试时机和OS的特性。这也是让我们实现TAkipi的原因之一。
延伸阅读
1.如果你对JVM的锁是如何的实现很感兴趣,可以查看这个源代码 http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/42ca4002efc2/src/share/vm/runtime/synchronizer.cpp
2. 所有关于Java同步API的介绍和技术 http://docs.oracle.com/javase/tutorial/essential/concurrency/index.html
3. Scala的并发 http://twitter.github.io/scala_school/concurrency.html
(全文完)如果您喜欢此文请点赞,分享,评论。
发表评论
-
JM总结
2017-01-11 10:41 4721、系统权限 时间戳,分配的key进行md5,动态的带过来 ... -
kafka
2016-05-26 15:25 293############################# S ... -
nginx+keepalive实现热切换部署
2015-09-04 20:53 0http://blog.csdn.net/e421083458 ... -
java中wait和sleep的区别
2015-03-01 21:26 0通常,多线程之间需要协调工作。例如,浏览器的一个显示图片的线 ... -
Synchronized和Static Synchronized区别
2015-03-01 18:50 0通过分析这两个用法的分析,我们可以理解java中锁的概念。 ... -
spring各版本下载导航
2015-02-11 14:29 0http://repo.spring.io/release/o ... -
参考博文
2015-02-06 14:59 0http://blog.csdn.net/ghsau/ar ... -
Struts2面试题
2015-02-06 11:15 0什么是Struts2Apache Struts2 ... -
Spring面试问题
2015-02-06 11:11 0什么是Spring?Spring是一个反转控制IOC和AO ... -
Java Collection集合面试题
2015-02-06 11:08 0Java集合Collection框架是什么?列出集合框架的一 ... -
Java企业系列面试题(集合篇)
2015-02-06 11:06 018 。什么是Java集合框 ... -
Java企业系列面试题(线程篇)
2015-02-06 11:05 011 。进程process和线程thread之间的区别是什么 ... -
Java企业系列面试题(基础篇)转
2015-02-06 11:04 0Java是一种基于类和面向对象的计算机编程语言。 面向对象的 ... -
多线程常见面试题
2015-02-06 11:00 0你有线程T1,T2和T3,你将如何确保线程T2运行后T1后, ... -
在Java中Lock接口比synchronized块的优势是什么
2014-12-18 18:21 3200http://blog.csdn.net/fw0124/a ... -
java join的用法
2014-12-18 18:13 518转自http://blog.csdn.net/anh ... -
解读dbcp自动重连那些事
2014-11-11 16:19 602数据库链接 常见的问题: 1. 数据库意外重启后 ... -
连接池未关闭问题的发现与解决
2014-11-11 16:18 1021最近项目上线,但是大下午的客服反应项目登录页面很慢,SA发 ... -
Log4j.properties配置详解
2014-11-10 17:40 527一、Log4j简介Log4j有三个主要的组件:Loggers ... -
spring aop中的propagation的7种配置的意思
2014-06-04 10:40 4591.前言。 在声明式的事务处理中,要配置一个切面,即一组方法 ...
相关推荐
该工具支持Java、Scala和Groovy三种语言,并且可以用于测试环境,需要在maven的pom.xml文件中添加对应的依赖。 在实际使用中,Awaitility提供了非常灵活的等待条件设置。例如,你可以通过调用`await().until...
要重点强调的是,本书并不仅仅只适合于java语言的并发编程,它还适用于clojure、groovy、jruby和scala等所有运行在jvm平台上的编程语言。 《java虚拟机并发编程》共10章,分为五个部分。第一部分:并发策略,阐释...
sbt-sonatype, 用于将 Scala/java项目发布到 Maven 中央的sbt插件 sbt-sonatype插件通过 Sonatype Nexus REST API 将你的项目发布到 Maven 中央知识库的sbt插件。 要将工件部署到Sonatype存储库,需要将项目同步到 ...
沙拉Salad包装了生菜异步和同步Java API,为Scala应用程序提供了惯用的API。 提供有效的SERDES(序列化器-反序列化器)以将键和值编码为纯字节数组或Snappy压缩字节数组。 CompactByteArraySerdes和SnappySerdes还将...
后端可以与各种Scala堆栈集成,从而提供同步和异步,过程和功能接口。 后端实现包括基于 , , , 和HTTP客户端的实现。 它们与 , , , , 和集成在一起。 受支持的Scala版本包括2.11、2.12、2.13和3。 这是...
适用于Java的AWS开发工具包为其客户端提供同步和异步API。 在异步环境(例如Spray或Akka HTTP路由)中使用同步API是有问题的。 不幸的是,使用异步API是有问题的,因为它们使用Java期货而不是Scala期货。 所有非流式...
MongoDB 同步 Scala 驱动程序 MongoDB 同步 Scala 驱动程序 (mssd) 基于普通的旧版并为其添加了一些 Scala 美感。 它类似于但具有不同的 api,其灵感来自于 bson 文档的和通用 API 的 。 此驱动程序正在进行中,...
Scala TON客户端 社区链接: Scala TON Client是绑定到的简单scala。 特征: TON SDK v 1.12.0的所有方法均已实现,但debot功能除外 通过同步异步调用与TON SDK进行交互 每个方法都包含inline-doc 自动下载适用...
不支持Eclipse OpenJ9,请确保JAVA_HOME环境变量指向有效的Java 8或11安装。 > = 10的节点才能使coc.nvim正常工作。 coc.nvim没有LSP命令的默认映射,因此您需要对其进行配置才能使任何命令正常工作。 您可以找到...
它扩展了以支持数据/事件序列,并添加了运算符,使您可以声明性地组成序列,同时抽象出对诸如低级线程,同步,线程安全和并发数据结构之类的问题的关注。 零依赖 <1MB的罐子 Java 6+和 2.3+ Java 8 Lambda支持...
play 包中的 API 是为 Java 开发者准备的,比如 play.mvc。对于 Scala 开发者,请使用 play.api.mvc。 参与翻译 首先,你得有个 Github 账号,然后: 打开,猛戳「Fork」按钮 把 Fork 到你账号下的项目 Clone 到本地...
它提供了完整的库集覆盖,并支持同步和异步请求。 此 SDK 可用于基于 JVM 的语言,如 Java、Kotlin、Scala。 使用图书馆 请参阅项目的最新版本。 这个库需要使用 Java8。 库的版本发布到 . 要包含库及其依赖项,请将...
概述简单数据类型-具有本机Java和Scala集合支持的Map , Set , Queue , SetMap和MultiMap 。 使用任何纯条件更新-无查询语言。 使用Transaction API进行原子更新和插入。 具有可配置API的非阻塞核心,用于阻塞,非...
java实战开发1200例源码将您的网站部署到 S3 这是项目的一个分支,合并了所有挂起的拉取请求。 s3_website能为您做什么 为您创建和配置 S3 网站 将您的静态网站上传到 AWS S3 , , 和 自动支持 帮助您使用 AWS ...
这里面的数字 2.12 和 1.13.1 分别表示了这个 JAR 包所依赖的 Scala 和 Flink 的版本。 具体来说: flink-connector-hive:表示这是一个 Flink 的 Hive 连接器。 _2.12:表示这个 JAR 包是为 Scala 2.12 版本编译...
语言:Java,Scala 其它:akka,jetty,curator,bootstrap 布署与启动 目录说明: $HONEY_HOME/conf下为系统配置 $HONEY_HOME/conf/supervisor-conf下为监控配置 $HONEY_HOME/lib为依赖包 $HONEY_HOME/bin...
Angel基于Java和Scala开发,能在社区的Yarn上直接调度运行,并基于PS Service,支持Spark on Angel,集成了部分图计算和深度学习算法。 欢迎对机器学习有兴趣的同仁一起贡献代码,提交Issues或者Pull Requests。请先...
此系列文章主要关于xposed的相关学习,以下所提及到的所有方式皆为学习,如有他人使用本系列学习文章中所提及的知识点用于其他非法用途,本人不承担由此造成的任何后果!! 更多的介绍可以参阅: 全部功能文档列表,...
go 简洁的并发 多核处理器越来越普及。...Java 中有一系列的线程同步的方法,go 里面有 goroutine(协程),先看下下面的代码执行的结果是什么呢? package main import ( fmt ) func main() {
看不到源码Java - React式后端示例 该存储库包含一组示例/模式,可用于使基于 Java 的微服务reactive 。 GOV.UK Pay是一个政府支付平台,由许多微服务组成。 我们的后端是默认的,我们用于大多数基于 Java 的微服务...