2019独角兽企业重金招聘Python工程师标准>>>

死锁:

经典例子:“哲学家进餐”问题。

死锁的解决:

数据库服务器解决死锁:数据库系统中考虑了检测死锁和从死锁中恢复。当数据库服务器检测到死锁时(通常在表示等待关系的有向图中搜索循环),将选择一个牺牲者并放弃这个事务。作为牺牲者的事务会放弃它持有的所有资源,从而使其他事务继续执行。然后可以重新执行被强制终止的事务。

JVM解决死锁:JVM在解决死锁只能终止并重启。

死锁的产生:

锁顺序死锁:

两个线程试图以不同的顺序来获得相同的锁,那么就用可能发生死锁。

public class LeftRightLock{private final Object left = new Object();private final Object right = new Object();public void leftRight(){synchronized(left){synchronized(right){doSomething();}}}public void rightLeft(){synchronized(right){synchronized(left){doSomething();}}}
}

解决方法:如果所有线程以固定的顺序来请求锁,那么在程序中就不会出现锁顺序死锁问题。

动态的锁顺序死锁:

有时候,并不能清楚地知道是否在锁顺序上有足够的控制权来避免死锁的发生。

public void transferMoney(Account fromAccount, Account toAccount){synchronized(fromAccount){synchronized(toAccount){doSomething();}}
}

上述方法可以用来实现A和B的转账,看似安全,但也有可能发生死锁。所有线程似乎都是按照相同的顺序来获得锁,实则不是。这里获得锁的顺序和传入的参数顺序有关,而这些参数又取决于外部输入。如果两个线程同时调用transferMoney方法,其中一个线程从X向Y转账,并一个线程从Y向X转账,嘛呢就会发生死锁:

  • A线程:transferMoney(myAccount, yourAccount);
  • B线程:transferMoney(yourAccount, myAccount);

那么该问题应该如何解决?要解决这种死锁,必须定义锁的顺序,在整个应用程序中都要按照这个顺序来获取锁。

定义锁的顺序可以使用System.identityHashCode方法,可以先获取所有要加锁对象的哈希值,每次要嵌套锁的时候都先获取哈希值大的对象,再获取哈希值小的对象。

int formHash = System.identityHashCode(formAcct);
int toHash = System.indntityHashCode(toAcct);if(formHash < toHash){synchronized(toHash){synchronized(fromHash){doSomething();}}
}else{synchronized(fromHash){synchronized(toHash){doSomething();}}
}

在协作对象之间发生的死锁:

有时候获取多个锁的操作并不像前两个例子那么明显,这两个锁并不一定在同一个方法中获取。

class Taxi{@GuardedBy("this") private Point location, destination;private final Dispacher dispacher;public Taxi(Dispacher dispacher){ this.dispacher = dispacher; }public synchronized Point getLocation(){ return location; }public synchronized void setLocation(Point location){this.location = location;if(location.equals(destination)dispatcher.notifyAvailable(this);}
}class Dispatcher{@GuardedBy("this") private final Set<Texi> taxis;@GuardedBy("this") private final Set<Texi> availableTaxis;public Dispatcher(){ taxis = new HashSet<Taxi>();  availableTaxis = new HashSet<Taxi>();}public synchronized void notifyAvailable(Taxi taxi){availableTaxis.add(taxi);}public synchronized Image getImage(){Image image = new Inage();for(Taxi t: taxis)image.drawMarker(t.getLocation());return image();}
}

尽管没有任何方法会显式获取两个锁,但setLocation和getImage等方法的调用者都会获得两个锁。如果一个线程在收到GPS更新事件时会调用setLocation,它会更新位置,然后判断是否到达的目的地,如果到达了,则会通知Dispatcher:获得一个新的目的地。因为setLocation和notifyAvailable都是同步方法,因此调用setLocation会先获取Texi的锁,然后获取Dispatcher的锁。同样,调用getImage会先获取Dispatcher的锁,再获取Taxi的锁。所以改代码可能发生死锁。

解决方法:通过开放调用来解决。

开放调用:

方法调用相当于一种抽象屏障,因而无需了解在被调用方法中被执行的操作。正是该原因,在持有锁的时候对调用某个外部方法难以进行分析,从而可能导致死锁。

如果在调用某个方法时不需要持有锁,这种调用被称为开放调用。依赖于开放调用的类通常能表现出更好的行为,并且与那些在调用方法时需要持有锁的类相比,也更容易编写。

下面代码对Texi和Dispatcher进行来写,通过开放调用使得同步代码块仅用于保护那些涉及共享的操作,从而消除了发生死锁的风险(避免了锁嵌套)。

class Taxi{@GuardedBy("this") private Point location, destination;private final Dispacher dispacher;...public void setLocation(Point location){boolean reacherDestination;synchronized (this){this.location = location;reacherDestination = location.equals(destination);}if(reacherDestination)dispatcher.notifyAvailable(this);}
}class Dispatcher{@GuardedBy("this") private final Set<Texi> taxis;@GuardedBy("this") private final Set<Texi> availableTaxis;...public synchronized Image getImage(){Set<Taxi> copy;synchronized (this){copy = new HashSet<Taxi>(taxis);}Image image = new Inage();for(Taxi t: taxis)image.drawMarker(t.getLocation());return image();}
}

资源死锁:

上面几种情况都是多个线程互相持有彼此正在等待的锁而又不释放自己持有的锁时产生的死锁。当多个线程在相同资源集合上等待时,也会发生死锁。

资源死锁的典型例子:银行家算法。

饥饿:

当线程无法访问它所需要的资源而不能继续执行时,就发生了饥饿现象。引发饥饿最常见的资源就是CPU时钟周期。如果在Java应用程序中对线程的优先级使用不当,或者在持有锁的时候执行一些无法结束的结构,那么也可能导致饥饿。

通常尽量不要更改线程的优先级,只要改变了线程的优先级,程序的行为就将与平台相关,并且会导致发生饥饿的风险。

活锁:

活锁是另一种形式的活跃性问题。该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复同样的操作,而且总会失败。活锁通常发生在处理事务消息中:如果不能成功处理某个消息,那么消息处理机制将回滚事务,并将它重新放到队列的开头。这样,错误的事务被一直回滚重复执行。这种形式的活锁通常是由过度的错误恢复代码造成的,因为它错误地将不可修复的错误认为是可修复的错误。

当多个相互协作的线程都对彼此进行相应而修改自己的状态,并使得任何一个线程都无法继续执行时,就导致了活锁。这就像两个过于礼貌的人在路上相遇:他们彼此让路,然后在另一条路上相遇,然后他们就一直这样避让下去。

要解决这种活锁问题,需要在重试机制中引入随机性。例如在网络上发送数据包,如果检测到冲突,都要停止并在一段时间后重发。如果都在1秒后重发,还是会冲突。所以引入随机性可以解决该类问题。

转载于:https://my.oschina.net/HuoQibin/blog/1808106

Java Concurrent--死锁/饥饿/活锁相关推荐

  1. 深耕Java多线程 - 死锁、活锁、饥饿

    文章目录 1. 什么是死锁? 2. 死锁产生的四个必要条件是什么? 3. 如何定位死锁? 3.1 jps+jstack 3.2 jconsole 4. 如何避免死锁? 5. 活锁 6. 饥饿 1. 什 ...

  2. java多线程中的死锁、活锁、饥饿、无锁都是什么鬼?

    转载自 java多线程中的死锁.活锁.饥饿.无锁都是什么鬼? 死锁.活锁.饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现了这三种情况,即线程不再活跃,不能再正常地执行下去了. 死锁 死锁 ...

  3. java 死锁和饥饿_死锁与活锁,死锁与饥饿的区别

    一.定义: 1.死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等 ...

  4. Java死锁、活锁,悲观锁、乐观锁

    1.死锁与活锁的区别,死锁与饥饿的区别? 死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去. 产生死锁的必要条件: 互斥条 ...

  5. 线程的死锁、活锁和饥饿现象

    目录 1.死锁 2.活锁 3.饥饿 一个资源应该单独使用一把锁. 比如,一个对象中有多个共享资源,但有多个线程需要使用其中的不同资源 此时如果把对象整体作为一把锁,那并发就很低. 可以考虑,把每个共享 ...

  6. 死锁与活锁的区别,死锁与饥饿的区别?

    **死锁:**是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去. 产生死锁的必要条件: 1.互斥条件:所谓互斥就是进程在某一时间 ...

  7. 【无标题】线程学习(18)-多把锁下的线程问题,死锁,活锁,饥饿

    多把锁的应用 减小锁粒度,提交并发度. package com.bo.threadstudy.four;import lombok.extern.slf4j.Slf4j;/*** 多把锁的情况,以及后 ...

  8. Java中的死锁与活锁

    活锁与死锁 活锁 活锁同样会发生在多个相互协作的线程间,当他们为了彼此间的响应而相互礼让,使得没有一个线程能够继续前进,那么就发生了活锁.同死锁一样,发生活锁的线程无法继续执行. 相当于两个在半路相遇 ...

  9. 死锁、活锁、饥饿定位死锁解决死锁

    文章目录 1. 死锁 2. 定位死锁 2.1 jstack工具使用 2.2 jconsole工具使用: 3. 解决死锁 3.1 哲学家就餐问题 4. 活锁 4.1 活锁原因 4.2 活锁解决 5. 饥 ...

最新文章

  1. 71 mac boook pro 无 gpu 下caffe 安装
  2. 在OperaMasks中使用ELite和JRuby动态语言的秘笈
  3. linux安装的mysql没有密码_linux系统安装的mysql数据库root帐户密码忘记的两种处理方法...
  4. IT团队之非正式沟通
  5. php如何对数据类型检测 有哪些方法,php检测数据类型的几种方法汇总
  6. QT的QDomDocument类的使用
  7. 使用MVC4,Ninject,EF,Moq,构建一个真实的应用电子商务SportsStore(一)
  8. 音乐播放类应用后台播放耗电评测报告
  9. 大脚战场插件怎么关闭_PM工具栏插件:HonmToolBar
  10. CUBA平台–新的Java企业应用程序框架
  11. ES6使用object的is()方法比较两个值
  12. 土是独体字结构吗_205砂浆、混凝土强度等级与定额不同时,你会调整换算吗?...
  13. Linux系统编程20:基础IO之从内核代码深刻理解Linux是如何管理文件及文件描述符的本质是什么
  14. 解封装(一):ffmpeg解封装
  15. 数字图像处理(六)——Matlab实现频域图像分析、FFT实现4:1的图像压缩
  16. [RTMP协议]常用直播流地址
  17. 苹果笔记本上网很慢怎么回事
  18. 广州大学机器学习与数据挖掘实验三
  19. 李宏毅2022《机器学习/深度学习》——学习笔记(1)
  20. 计算机用word做海报,如何用Word文档做出一张简单的海报!

热门文章

  1. 基于特征的对抗迁移学习论文_[论文笔记] 对抗样本不是bugs,而是特征
  2. 游戏账号交易平台网站源码
  3. 创建代码生成器可以很简单:如何通过T4模板生成代码?[上篇]
  4. 一个简单的性能计数器:CodeTimer
  5. Linux: 传参数给alias
  6. UIActivityIndicatorView、UIProgressView 活动与进度指示器 (实例)
  7. centos7/rhel7安装较高版本ruby2.2/2.3/2.4+
  8. 除了ssh外也可以开启telnet服务连接服务器
  9. LeetCode 566. Reshape the Matrix
  10. 127.0.0.0与0.0.0.0的区别