- 浏览: 50805 次
- 性别:
- 来自: 北京
-
文章分类
最新评论
原文: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 5361、系统权限 时间戳,分配的key进行md5,动态的带过来 ... -
kafka
2016-05-26 15:25 368############################# 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 3314http://blog.csdn.net/fw0124/a ... -
java join的用法
2014-12-18 18:13 566转自http://blog.csdn.net/anh ... -
解读dbcp自动重连那些事
2014-11-11 16:19 658数据库链接 常见的问题: 1. 数据库意外重启后 ... -
连接池未关闭问题的发现与解决
2014-11-11 16:18 1068最近项目上线,但是大下午的客服反应项目登录页面很慢,SA发 ... -
Log4j.properties配置详解
2014-11-10 17:40 577一、Log4j简介Log4j有三个主要的组件:Loggers ... -
spring aop中的propagation的7种配置的意思
2014-06-04 10:40 5211.前言。 在声明式的事务处理中,要配置一个切面,即一组方法 ...
相关推荐
对于Java开发者而言,学习Scala不仅能帮助他们在现有技能的基础上进一步提升,还能为解决现代软件工程中的挑战提供新的工具和技术。Scala以其独特的设计理念和强大的功能集,已经成为越来越多开发者的首选语言之一。...
这种模型避免了传统多线程中的同步问题,简化了并发编程的复杂性。 Scala的函数式编程特性如高阶函数、不可变数据结构和柯里化,使得编写简洁、无副作用的代码成为可能,这对于构建大规模并发应用尤其有益。同时,...
《Scala程序设计:Java虚拟机多核编程实战》是一本专为程序员和有经验的Java开发者设计的书籍,旨在引导读者掌握Scala语言,利用其功能强大的特性进行并发编程。Scala是一种融合了函数式和面向对象编程特点的静态...
Scala 是一种静态类型的、面向对象和函数式的编程语言,它运行在 Java 平台上,利用了 JVM 的强大功能。TypeScript 则是 JavaScript 的超集,添加了静态类型和其他高级特性,用于构建大型、可维护的前端应用程序。 ...
2. **函数式编程**:Scala中的纯函数、不可变数据结构、柯里化、尾递归和Monad等概念。 3. **面向对象编程**:Scala如何融合面向对象和函数式编程,包括特质(trait)和混合类型(mixin)。 4. **并发编程**:Scala...
5. ** Actors 和 Concurrency**:Scala内置了Actors模型,用于构建并发和分布式系统,帮助开发者避免线程同步问题。 6. **高级特性**:涵盖类型参数、抽象类型、特质、包对象、混合类型和匿名函数等,这些特性使...
由于Scala可以与Java无缝互操作,因此Scala中的actors模型对Java程序员来说也是一个值得研究和应用的并发模型。此外,鉴于Scala的actors模型和Akka库在并发与分布式系统领域中的应用越来越广泛,本书也适合那些希望...
8. ** Actors和Concurrent Programming**:Scala提供Actors模型支持并发编程,这是一种轻量级的线程模型,能够简化多线程同步问题。 9. **Akka框架**:虽然不是Scala语言的一部分,但Akka是一个流行的用Scala编写的...
Scala编程完整版 Scala,全称“Scalable Language”...无论你是Java开发者希望扩展技能,还是对函数式编程感兴趣,Scala都值得你投入时间和精力去学习。《Scala编程完整版》这本书将是你探索这个语言世界的宝贵资源。
解压缩后,将`bin`目录添加到你的系统路径中,这样你就可以在命令行中使用`scala`命令来启动REPL(Read-Eval-Print Loop)或编译Scala源代码。同时,`lib`目录包含了Scala运行库,供其他项目引用。 总的来说,Scala...
6. ** Actors 和 Concurrency**:Scala的Actors模型提供了一种安全的并发处理方式,它通过消息传递来管理共享状态,降低了线程同步的复杂性。 7. **集合库**:Scala的集合库非常强大,提供了各种高效的集合实现,如...
通过上述知识点的介绍,我们可以看出Scala不仅具备现代编程语言所应有的一切特性,而且在设计上特别强调简洁性、灵活性和安全性。无论是面向对象编程还是函数式编程,Scala都能提供强大的支持。此外,Scala与JVM的...
- Scala-IDE和Maven都能够处理Scala和Java代码的交叉编译,确保在构建过程中正确地处理引用关系。 遵循以上步骤,你就成功地建立了一个Eclipse+Scala+maven的开发环境,可以进行Scala Web项目的开发。这个环境不仅...
学习Scala不仅能够让你掌握一种强大而现代的编程语言,还能带你进入函数式编程的世界,提升解决问题的思路和能力。无论你是Java开发者希望拓宽视野,还是对函数式编程感兴趣,Scala都是一个值得投入时间和精力的学习...
通过上述步骤,你已经学会了如何在IDEA中创建Scala项目并打包指定依赖。这对于Scala项目的开发和部署非常有帮助。记得定期更新IDEA版本和Scala插件,以获得最佳的开发体验。此外,合理配置依赖可以显著提高项目的...
虽然可变集合在某些场景下可能带来便利,但在多线程环境中应谨慎使用,以避免数据竞争和同步问题。Scala提供的可变集合包括`ArrayBuffer`、`Array`等。 #### 示例分析 为了更好地理解Scala集合类的强大之处,考虑...
通过安装这个插件,用户可以在IntelliJ IDEA中编写、运行和调试Scala代码,享受到与Java开发类似的高效体验。以下是该插件的一些核心功能: 1. **语法高亮与代码提示**:Scala插件为Scala语法提供了颜色编码,使...
项目中,Scala源文件占据绝大多数,显示了Scala在数据处理领域的应用优势,这可能得益于Scala语言简洁的语法和强大的函数式编程特性。与此同时,Java源文件虽然数量较少,但依旧承担了关键的服务功能模块,表明Java...
- ** Actors模型**:Scala内置对Akka框架的支持,允许并发和分布式计算,而不会遇到线程同步的问题。 - **复合性管理**:通过特质(Traits)和柯里化(Currying),Scala提供了一种组织复杂代码结构的方式。 - **...
Scala是一种强大的、现代的编程语言,它在IT领域中被广泛应用,特别是在大数据处理和分布式计算中,例如Apache Spark就是基于Scala开发的。标题中的"scala-2.11.12.msi"指的是Scala的特定版本2.11.12的Windows安装...