深入浅出 Java Concurrency (37): 并发总结 part 1 死锁与活跃度[转]
死锁与活跃度
前面谈了很多并发的特性和工具,但是大部分都是和锁有关的。我们使用锁来保证线程安全,但是这也会引起一些问题。
- 锁顺序死锁(lock-ordering deadlock):多个线程试图通过不同的顺序获得多个相同的资源,则发生的循环锁依赖现象。
- 动态的锁顺序死锁(Dynamic Lock Order Deadlocks):多个线程通过传递不同的锁造成的锁顺序死锁问题。
- 资源死锁(Resource Deadlocks):线程间相互等待对方持有的锁,并且谁都不会释放自己持有的锁发生的死锁。也就是说当现场持有和等待的目标成为资源,就有可能发生此死锁。这和锁顺序死锁不一样的地方是,竞争的资源之间并没有严格先后顺序,仅仅是相互依赖而已。
锁顺序死锁
最经典的锁顺序死锁就是LeftRightDeadLock.
final Object left = new Object();
final Object right = new Object();
public void doLeftRight() {
synchronized (left) {
synchronized (right) {
execute1();
}
}
}
public void doRightLeft() {
synchronized (right) {
synchronized (left) {
execute2();
}
}
}
private void execute2() {
}
private void execute1() {
}
}
这个例子很简单,当两个线程分别获取到left和right锁时,互相等待对方释放其对应的锁,很显然双方都陷入了绝境。
动态的锁顺序死锁
与锁顺序死锁不同的是动态的锁顺序死锁只是将静态的锁变成了动态锁。 一个比较生动的例子是这样的。
Account toAccount,//
int amount
) {
synchronized (fromAccount) {
synchronized (toAccount) {
fromAccount.decr(amount);
toAccount.add(amount);
}
}
}
当我们银行转账的时候,我们期望锁住双方的账户,这样保证是原子操作。 看起来很合理,可是如果双方同时在进行转账操作,那么就有可能发生死锁的可能性。
很显然,动态的锁顺序死锁的解决方案应该看起来和锁顺序死锁解决方案差不多。 但是一个比较特殊的解决方式是纠正这种顺序。 例如可以调整成这样:
public void transferMoney(Account fromAccount,//
Account toAccount,//
int amount
) {
int order = fromAccount.name().compareTo(toAccount.name());
Object lockFirst = order>0?toAccount:fromAccount;
Object lockSecond = order>0?fromAccount:toAccount;
if(order==0){
synchronized(lock){
synchronized(lockFirst){
synchronized(lockSecond){
//do work
}
}
}
}else{
synchronized(lockFirst){
synchronized(lockSecond){
//do work
}
}
}
}
这个挺有意思的。比较两个账户的顺序,保证此两个账户之间的传递顺序总是按照某一种锁的顺序进行的, 即使多个线程同时发生,也会遵循一次操作完释放完锁才进行下一次操作的顺序,从而可以避免死锁的发生。
资源死锁
资源死锁比较容易理解,就是需要的资源远远大于已有的资源,这样就有可能线程间的资源竞争从而发生死锁。 一个简单的场景是,应用同时从两个连接池中获取资源,两个线程都在等待对方释放连接池的资源以便能够同时获取 到所需要的资源,从而发生死锁。
资源死锁除了这种资源之间的直接依赖死锁外,还有一种叫线程饥饿死锁(thread-starvation deadlock)。 严格意义上讲,这种死锁更像是活跃度问题。例如提交到线程池中的任务由于总是不能够抢到线程从而一直不被执行, 造成任务的“假死”状况。
除了上述几种问题外,还有协作对象间的死锁以及开发调用的问题。这个描述起来会比较困难,也不容易看出死锁来。
避免和解决死锁
通常发生死锁后程序难以自恢复。但也不是不能避免的。 有一些技巧和原则是可以降低死锁可能性的。
最简单的原则是尽可能的减少锁的范围。锁的范围越小,那么竞争的可能性也越小。 尽快释放锁也有助于避开锁顺序。如果一个线程每次最多只能够获取一个锁,那么就不会产生锁顺序死锁。尽管应用中比较困难,但是减少锁的边界有助于分析程序的设计和简化流程。 减少锁之间的依赖以及遵守获取锁的顺序是避免锁顺序死锁的有效途径。
另外尽可能的使用定时的锁有助于程序从死锁中自恢复。 例如对于上述顺序锁死锁中,使用定时锁很容易解决此问题。
boolean over = false;
while (!over) {
if (left.tryLock(1, TimeUnit.SECONDS)) {
try {
if (right.tryLock(1, TimeUnit.SECONDS)) {
try {
execute1();
} finally {
right.unlock();
over = true;
}
}
} finally {
left.unlock();
}
}
}
}
public void doRightLeft() throws Exception {
boolean over = false;
while (!over) {
if (right.tryLock(1, TimeUnit.SECONDS)) {
try {
if (left.tryLock(1, TimeUnit.SECONDS)) {
try {
execute2();
} finally {
left.unlock();
over = true;
}
}
} finally {
right.unlock();
}
}
}
}
看起来代码会比较复杂,但是这是避免死锁的有效方式。
活跃度
对于多线程来说,死锁是非常严重的系统问题,必须修正。除了死锁,遇到很多的就是活跃度问题了。 活跃度问题主要包括:饥饿,丢失信号,和活锁等。
饥饿
饥饿是指线程需要访问的资源被永久拒绝,以至于不能在继续进行。 比如说:某个权重比较低的线程可能一直不能够抢到CPU周期,从而一直不能够被执行。
也有一些场景是比较容易理解的。对于一个固定大小的连接池中,如果连接一直被用完,那么过多的任务可能由于一直无法抢占到连接从而不能够被执行。这也是饥饿的一种表现。
对于饥饿而言,就需要平衡资源的竞争,例如线程的优先级,任务的权重,执行的周期等等。总之,当空闲的资源较多的情况下,发生饥饿的可能性就越小。
弱响应性
弱响应是指,线程最终能够得到有效的执行,只是等待的响应时间较长。 最常见的莫过于GUI的“假死”了。很多时候GUI的响应只是为了等待后台数据的处理,如果线程协调不好,很有可能就会发生“失去响应”的现象。
另外,和饥饿很类似的情况。如果一个线程长时间独占一个锁,那么其它需要此锁的线程很有可能就会被迫等待。
活锁
活锁(Livelock)是指线程虽然没有被阻塞,但是由于某种条件不满足,一直尝试重试,却终是失败。
考虑一个场景,我们从队列中拿出一个任务来执行,如果任务执行失败,那么将任务重新加入队列,继续执行。假如任务总是执行失败,或者某种依赖的条件总是不满足,那么线程一直在繁忙却没有任何结果。
错误的循环引用和判断也有可能导致活锁。当某些条件总是不能满足的时候,可能陷入死循环的境地。
线程间的协同也有可能导致活锁。例如如果两个线程发生了某些条件的碰撞后重新执行,那么如果再次尝试后依然发生了碰撞,长此下去就有可能发生活锁。
解决活锁的一种方案是对重试机制引入一些随机性。例如如果检测到冲突,那么就暂停随机的一定时间进行重试。这回大大减少碰撞的可能性。
另外为了避免可能的死锁,适当加入一定的重试次数也是有效的解决办法。尽管这在业务上会引起一些复杂的逻辑处理。
深入浅出 Java Concurrency (37): 并发总结 part 1 死锁与活跃度[转]的更多相关文章
- 《深入浅出 Java Concurrency》—并发容器 ConcurrentMap
(转自:http://blog.csdn.net/fg2006/article/details/6404226) 在JDK 1.4以下只有Vector和Hashtable是线程安全的集合(也称并发容器 ...
- 深入浅出 Java Concurrency (21): 并发容器 part 6 可阻塞的BlockingQueue (1)[转]
在<并发容器 part 4 并发队列与Queue简介>节中的类图中可以看到,对于Queue来说,BlockingQueue是主要的线程安全版本.这是一个可阻塞的版本,也就是允许添加/删除元 ...
- 深入浅出 Java Concurrency (16): 并发容器 part 1 ConcurrentMap (1)[转]
从这一节开始正式进入并发容器的部分,来看看JDK 6带来了哪些并发容器. 在JDK 1.4以下只有Vector和Hashtable是线程安全的集合(也称并发容器,Collections.synchro ...
- 深入浅出 Java Concurrency (27): 并发容器 part 12 线程安全的List/Set[转]
本小节是<并发容器>的最后一部分,这一个小节描述的是针对List/Set接口的一个线程版本. 在<并发队列与Queue简介>中介绍了并发容器的一个概括,主要描述的是Queue的 ...
- 深入浅出 Java Concurrency (25): 并发容器 part 10 双向并发阻塞队列 BlockingDeque[转]
这个小节介绍Queue的最后一个工具,也是最强大的一个工具.从名称上就可以看到此工具的特点:双向并发阻塞队列.所谓双向是指可以从队列的头和尾同时操作,并发只是线程安全的实现,阻塞允许在入队出队不满足条 ...
- 深入浅出 Java Concurrency (17): 并发容器 part 2 ConcurrentMap (2)[转]
本来想比较全面和深入的谈谈ConcurrentHashMap的,发现网上有很多对HashMap和ConcurrentHashMap分析的文章,因此本小节尽可能的分析其中的细节,少一点理论的东西,多谈谈 ...
- 深入浅出 Java Concurrency (40): 并发总结 part 4 性能与伸缩性[转]
性能与伸缩性 使用线程的一种说法是为了提高性能.多线程可以使程序充分利用闲置的资源,提高资源的利用率,同时能够并行处理任务,提高系统的响应性. 但是很显然,引入线程的同时也引入了系统的复杂性.另外系统 ...
- 深入浅出 Java Concurrency (39): 并发总结 part 3 常见的并发陷阱
常见的并发陷阱 volatile volatile只能强调数据的可见性,并不能保证原子操作和线程安全,因此volatile不是万能的.参考指令重排序 volatile最常见于下面两种场景. a. 循环 ...
- 深入浅出 Java Concurrency (38): 并发总结 part 2 常见的并发场景[转]
常见的并发场景 线程池 并发最常见用于线程池,显然使用线程池可以有效的提高吞吐量. 最常见.比较复杂一个场景是Web容器的线程池.Web容器使用线程池同步或者异步处理HTTP请求,同时这也可以有效的复 ...
随机推荐
- Auto.js淘宝领喵币
最近,淘宝的新玩法,一直在充斥我的眼球,尤其是喵币的,盖楼... .... 于是就进去看了看,发现逛逛店铺,给好多喵币,但是要进20个,每个要15秒,好麻烦,于是就上网搜了一下,有没有脚本 因为之前搞 ...
- vue+nginx配置二级域名
[1]修改路由文件 [2]修改配置文件 [3]修改本机nginx配置文件 [4]修改服务器nginx配置文件 [5]重启nginx文件,用二级域名访问 http://192.168.199.xxx:7 ...
- HIVE文件
注册表的本地实体文件, 察看位置,以及映射本地文件到注册表中的位置, HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\hivelist 在这里写 ...
- Servlet接口的抽象方法实现
1.init:初始化方法,在Servlet被创建时执行,只会执行一次2.service:提供服务,每此Servelet被访问时service都会执行3.destroy:销毁方法,在服务器正常关闭时执行 ...
- pycharm连接数据库及相应操作
1.连接数据库 2.pycharm中数据库的操作
- fso文件夹操作用法实操
Sub 订单转换()Application.ScreenUpdating = FalseOn Error Resume Next Dim fso, fl, m%, n%, p%, q& Dim ...
- VS环境下,DEV插件的ComboBoxEdit控件最简单的数据源绑定和获取方法
使用 ComboBoxEdit 控件绑定key/value值: 因为 ComboBoxEdit 没有 DataSource 属性,所以不能直接绑定数据源,只能一项一项的添加. 首先创建一个类ListI ...
- Jinja2模板引擎
这里是Jinja2通用模板语言的文档. Jinja2 在其是一个 Python 2.4 库之前,被设计 为是灵活.快速和安全的.如果你接触过其它的基于文本的模板语言,比如 Smarty 或 Djang ...
- android 休眠状态下 后台数据上传
下面来说一下黑屏情况下传递数据: 要实现程序退出之后,仍然可以传递数据,请求网络,必须采用service,service可以保持在后台一直运行,除非系统资源极其匮乏,否则一般来说service是不会被 ...
- drupal7 代码生成用户,并自动登录
直接上代码 1. 生成用户(注册) $edit = [ "name" => "name", "pass" => "pa ...