一、避免活跃性危险
活跃性没有明确的定义。安全性的含义是“永远不发生糟糕的事情”,而活跃性则关注于另一个目标,即“某件正确的事情
最终会发生”。当某个操作无法继续执行下去时,就会发生活跃性问题。在串行程序中,活跃性问题的形式之一就是无意中造成的
无限循环,从而使循环之后的代码无法得到执行。线程将带来其他一些活跃性问题。例如,如果线程 A在等待线程B释放其持有的资源,而线程B永远都不释放该资源,那么A就会永久地等待下去。本次将介绍各种形式的活跃性问题,以及如何避免这些问题, 包括
死锁,饥饿,以及活锁。
在安全性与活跃性之间通常存在着某种制衡。我们使用加锁机制来确保线程安全,但如果过度地使用加锁,则可能导致因锁的顺序死锁(Lock-Ording Deadlock),这通常是因为需要获得两个及以上的锁时发生。同样,我们使用线程池和信号量来限制对资源的使用,但这些限制的行为可能会导致资源死锁(Resource Deadlock)。 Java 应用程序无法从死锁中恢复过来,因此在设计时一定要排除那些可能导致死锁的条件。
1.1 死锁
经典的“哲学家进餐”问题很好的描述了死锁情况。有五个哲学家绕着圆桌坐,每个哲学家面前有一盘面,两人之间有一支筷子,这样每个哲学家左右各有一支筷子。哲学家有2个状态,思考或者拿起筷子吃饭。如果哲学家拿到一只筷子,不能吃饭,直到拿到2只才能吃饭,并且一次只能拿起身边的一支筷子。一旦拿起便不会放下筷子直到把饭吃完,此时才把这双筷子放回原处。如果,很不幸地,每个哲学家拿起他或她左边的筷子,那么就没有人可以吃到了这就会造成死锁了。
由上面也可以看出是因为每个哲学家需要获取两个共享资源,而且存在环路依赖关系才导致的死锁。
1.1.1 Lock-ording deadlock
两个线程试图以不同的顺序来获得相同的锁。如果按照相同的顺序来请求锁,那么就不会出现循环的加锁依赖性,因此也不会产生死锁。再看看哲学家问题,正是这种情况。
想要验证锁顺序的一致性,需要对程序中的加锁行为进行全局分析。单独分析每条获取多个锁的路径是不够的,因为单独看每个获取锁的方式看起来都是“合理”的。
但有时候并不能清楚地知道是否在锁顺序上有足够的控制权来避免死锁的发生。看如下转账的代码:
所有的线程似乎都是按照相同的顺序获得锁,但事实上锁的顺序取决于传递给加锁方法的参数顺序,而这些参数顺序又取决于外部输入,如果一个线程从X向Y转账,另一个线程从Y向X转账,就会发生死锁。
解决此问题必须定义锁的顺序,并在整个应用程序中都按照这个顺序来获取锁。制定锁的顺序时,要按照一个稳定的排序方式来排,比如通过System.identityHashCode方法,返回Object.hashCode值来作为排序的比较。
上面例子使用了加时锁,虽然增加了代价,但保证了安全性,而且使用加时锁的几率也非常低。如果排序时能获得唯一的键值进行排序那就更容易了,比如银行账号就可以用来作为唯一的键值,不需要加时锁。
1.1.2 在协作对象之间发生的死锁
某些获取多个锁的操作不像上面那么容易判断,获取两个锁的操作并不一定必须在一个方法中被获取。如A类的同步方法里使用了B类的同步方法,B类的同步方法也使用了A类的同步方法,那么一个线程使用A类,一个线程使用B类,也会出现交叉等待,
协同死锁。
1.1.3 开放调用
上面的情况,A类和B类并不知道他们会陷入死锁,而且他们本来也不应该知道。方法调用相当于一种抽象屏障,因而你无需了解在被调用方法中所执行的操作。也正是因为这种屏障,所以也难以分析可能出现的死锁。
如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用(Open Call).可以通过开放调用来避免死锁的发生,类似于采用封装机制来提供线程安全的的方法。
1.1.4 资源死锁
正如当多个线程相互持有彼此正在等待的锁而又不释放自己已持有的锁时会发生死锁。当他们在相同的资源集合上等待时,也会发生死锁。
1.2 死锁的避免与诊断
如果一个程序每次至多只获得一个锁,那么就不会产生lock-ording deadlock,但这并不现实。在使用细粒度的锁的过程中,可以通过使用一种两阶段策略来检查代码中的死锁:
(1)首先找出在什么地方将获取多个锁(使这个集合尽可能小),
(2)然后对所有这些实例进行全局分析,从而确保他们在整个程序中获取锁的顺序都保持一致。尽可能使用开放调用。
1.2.1 支持定时的锁
这一项技术可以检测死锁和从死锁中恢复过来,即显式使用Lock类中的定时
tryLock功能来代替内置锁机制。
1.2.2 通过线程转储信息来分析死锁
Jvm可以通过线程转储(Thread Dump)来帮助识别死锁的发生。
1.3 其他活跃性危险
尽管死锁是最常见的活跃性危险,但在并发程序中还存在一些其他的活跃性危险,包括饥饿,丢失信号,活锁等。
饥饿:线程由于无法访问它所需要的资源而不能继续执行。最常见的资源就是CPU时钟周期,如优先级低的线程由于其他高优先级线程一直执行导致无法获得cpu时间。
活锁:该问题不会阻塞线程,但也不能继续执行,因为线程不断重复执行相同的操作,而且总会失败。当多个相互协作的线程对彼此进行退让,修改各自的状态,但反复避让,导致活锁。就像两个相遇的人互相让路交叉等待。
- 大小: 14.2 KB
- 大小: 64.7 KB
- 大小: 74.8 KB
- 大小: 144.1 KB
- 大小: 26.1 KB
分享到:
相关推荐
1.3.2 活跃性问题 1.3.3 性能问题 1.4 线程无处不在 第一部分 基础知识 第2章 线程安全性 2.1 什么是线程安全性 2.2 原子性 2.2.1 竞态条件 2.2.2 示例:延迟初始化中的竞态条件 2.2.3 复合操作 2.3 加锁...
1.3.2 活跃性问题 1.3.3 性能问题 1.4 线程无处不在 第一部分 基础知识 第2章 线程安全性 2.1 什么是线程安全性 2.2 原子性 2.2.1 竞态条件 2.2.2 示例:延迟初始化中的竞态条件 2.2.3 复合操作 2.3 加锁...
真正的Addison-Wesley 出品的Java Concurrency in Practice...第4部分 高级主题 第13章 显示锁 第14章 构建自定义的同步工具 第15章 原子变量与非阻塞同步机制 第16章 java存储模型 附录a 同步annotation 参考文献 索引
Java 5以及6在开发并发程序取得了显著的进步,提高了Java虚拟机的性能,提高了并发类的可伸缩性,并加入了丰富的新并发构建块。在本书中,这些便利工具的创造者不仅解释了它们究竟如何工作、如何使用,同时,还阐释...
活跃性与性能 串一串 Java 并发编程的知识点 愚以为,Java 并发编程的难点在于,反常识!因为并发、并行本身,是有悖于我们大脑的工作模式的,也就是说,我们长期的写码不得不 而一旦将这一个一个的线程组合起来,...
中文完整版的Java并发编程实践PDF电子书 作者:Brian Gogetz Tim Peierls Joshua Bloch Joseph Bowbeer David Holmes Doug Lea 译者:韩锴 方秒 目录 第1章 介绍 1.1 并发的(非常)简短历史 1.2 线程的优点 1.3 ...
中文完整版的Java并发编程实践PDF电子书 作者:Brian Gogetz Tim Peierls Joshua Bloch Joseph Bowbeer David Holmes Doug Lea 译者:韩锴 方秒 目录 第1章 介绍 1.1 并发的(非常)简短历史 1.2 线程的优点 1.3 ...
java并发编程(中文版,pdf格式),本书共16章,主要有四部分,第一部分:基础知识,第二部分:结构化并发应用程序,第三部分:活跃性、性能与测试,第四部分:高级主题。
MySQL的主要优点包括: - 开源且免费,且社区活跃,可以及时得到支持和更新。...- 提供高级安全性功能,如用户管理、访问控制、数据加密等。 - 提供丰富灵活的API和工具,支持多种编程语言、数据格式、存储引擎等。
Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。 Netty 是一个广泛使用的 Java 网络编程框架(Netty 在 2011 年获得了Duke's Choice Award,见...
总体构造本身具有一般性,也就是抽象性、实际问题无关性;局部构件具有通用性。也就是说,这里存在容器和容量的区别,构造是容器,实际问题是装在容器中的量。一个好的容器要能顶住容量的压力;一个好的建筑架构要能...
MyISAM InnoDB 区别 InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定。基本的差别为:MyISAM类型不支持事务处理等...而我的整体数据库服务器平均负载都在0.5-1左右。
最新的Oracle数据库11g特性:查询结果集缓存、自动内存管理、实时应用程序测试、高级压缩、全面回忆、活跃数据守卫选项以及对OLAP选项的变更(作为物化视图进行透明访问和管理)、Flashback事务命令、透明数据加密、...
5.4.1 并发访问的问题 100 5.4.2 SQL Server中的锁 101 5.4.3 查看活跃事务 102 5.4.4 事务隔离级别 104 5.4.5 事务隔离级别的设置 104 5.5 事务的阻塞 105 5.6 死锁 107 5.6.1 死锁的产生 107 5.6.2 处理死锁 108 ...
第1章 Oracle体系结构 1 1.1 数据库概述及实例 1 1.2 数据库 1 1.2.1 表空间 2 1.2.2 文件 2 1.3 实例 3 1.4 数据库内部结构 3 1.4.1 表、列和数据类型 4 1.4.2 约束条件 5 1.4.3 抽象数据类型 6 1.4.4 分区和子分区...
不仅理论全面、深入,抓住了重点和难点,还包含两个综合性案例,极具实战意义。《Linux高性能服务器编程》共17章,分为3个部分:第一部分对Linux服务器编程的核心基础——TCP/IP协议进行了深入的解读和阐述,包括TCP...