在安全性和活跃性之间通常存在着某种制衡

一、死锁

定义:在线程A持有锁L并想获得锁M的同时,线程B持有锁M并尝试获得锁L,线程AB均不会释放自己的锁,那么这两个线程将永远地等待下去

在数据库系统的设中考虑了检测死锁以及从死锁中恢复。JVM没有办法解决死锁,只能在编程和测试时注意不要让死锁发生

1、锁顺序死锁——两个线程试图以不同的顺序来获得相同的锁

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

2、动态的锁顺序死锁

  1. 1 A: transferMoney(myAccount, yourAccount, 10);
  2. 2 B: transferMoney(yourAccount, myAccount, 20);

使用Account中包含的唯一的不可变的并且具备可比性的键值或计算HashCode作为加锁顺序的依据

3、在协作对象间发生死锁

如果在持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。

  1. 举例:在协作对象间发生死锁
  1. class Taxi {
  2. @GuardedBy("this") private Point location, destination;
  3. private final Dispatcher dispatcher;
  4.  
  5. public Taxi(Dispatcher dispatcher) {
  6. this.dispatcher = dispatcher;
  7. }
  8.  
  9. public synchronized Point getLocation() {
  10. return location;
  11. }
  12.  
  13. public synchronized void setLocation(Point location) {
  14. this.location = location;
  15. if (location.equals(destination))
  16. dispatcher.notifyAvailable(this);
  17. }
  18.  
  19. public synchronized Point getDestination() {
  20. return destination;
  21. }
  22.  
  23. public synchronized void setDestination(Point destination) {
  24. this.destination = destination;
  25. }
  26. }
  27.  
  28. class Dispatcher {
  29. @GuardedBy("this") private final Set<Taxi> taxis;
  30. @GuardedBy("this") private final Set<Taxi> availableTaxis;
  31.  
  32. public Dispatcher() {
  33. taxis = new HashSet<Taxi>();
  34. availableTaxis = new HashSet<Taxi>();
  35. }
  36.  
  37. public synchronized void notifyAvailable(Taxi taxi) {
  38. availableTaxis.add(taxi);
  39. }
  40.  
  41. public synchronized Image getImage() {
  42. Image image = new Image();
  43. for (Taxi t : taxis)
  44. image.drawMarker(t.getLocation());
  45. return image;
  46. }
  47. }

taxi.setLocation(location) 和dispatcher.notifyAvailable(taxi)同时调用时会发生锁顺序死锁

4、开放调用——在调用某个方法时不需要持有锁

在程序中应尽量使用开放调用。与那些在持有锁时调用外部方法的程序相比,更易于对依赖于开放调用的程序进行死锁分析

  1. 举例:修改上例使用开放调用避免死锁
  1. class Taxi {
  2. @GuardedBy("this") private Point location, destination;
  3. private final Dispatcher dispatcher;
  4.  
  5. public Taxi(Dispatcher dispatcher) {
  6. this.dispatcher = dispatcher;
  7. }
  8.  
  9. public synchronized Point getLocation() {
  10. return location;
  11. }
  12.  
  13. public synchronized void setLocation(Point location) {
  14. boolean reachedDestination;
  15. synchronized (this) {
  16. this.location = location;
  17. reachedDestination = location.equals(destination);
  18. }
  19. if (reachedDestination)
  20. dispatcher.notifyAvailable(this);
  21. }
  22.  
  23. public synchronized Point getDestination() {
  24. return destination;
  25. }
  26.  
  27. public synchronized void setDestination(Point destination) {
  28. this.destination = destination;
  29. }
  30. }
  31.  
  32. @ThreadSafe
  33. class Dispatcher {
  34. @GuardedBy("this") private final Set<Taxi> taxis;
  35. @GuardedBy("this") private final Set<Taxi> availableTaxis;
  36.  
  37. public Dispatcher() {
  38. taxis = new HashSet<Taxi>();
  39. availableTaxis = new HashSet<Taxi>();
  40. }
  41.  
  42. public synchronized void notifyAvailable(Taxi taxi) {
  43. availableTaxis.add(taxi);
  44. }
  45.  
  46. public Image getImage() {
  47. Set<Taxi> copy;
  48. synchronized (this) {
  49. copy = new HashSet<Taxi>(taxis);
  50. }
  51. Image image = new Image();
  52. for (Taxi t : copy)
  53. image.drawMarker(t.getLocation());
  54. return image;
  55. }
  56. }

5、资源死锁——两个线程分别持有彼此想要的资源而又不会释放

例:任务执行需要连接两个数据库,两个任务分别连接了其中一个数据库,而又等待彼此释放另一个数据库的资源

6、线程饥饿死锁——一个任务中提交另一个任务,并一直等待被提交任务完成

这些任务往往是产生线程饥饿死锁的主要来源,有界线程池 / 资源池与相互依赖的任务不能一起使用。

二、死锁的避免与诊断

如果一个线程每次至多只能获得一个锁,那么就不会产生锁顺序死锁。

如果必须获取多个锁,那么在设计时必须考虑锁的顺序:尽量减少潜在的加锁交互数量,将获取锁时需要遵循的协议写入正式文档并始终遵循这些文档。

1、支持定时锁

显式使用Lock类中的定时tryLock功能来代替内置锁机制,显式锁则可以指定一个超时时限,在等待超过该时间后tryLock会返回一个失败信息

  • 当定时锁失败时,并不能确定是否由于死锁导致失败

即使不使用定时锁,使用能定时的锁,如果在获取锁时超时,那么可以释放当前的锁,在一段时间后再次尝试,从而消除了死锁发生的条件(在同时获取两个锁时有效)

2、通过线程转储信息来分析死锁

  线程转储包括各个运行中的线程的栈追踪信息,这类似于发生异常时的栈追踪信息。线程转储还包括加锁信息,例如每个线程持有了哪些锁,在哪些栈帧中获得这些锁,以及被阻塞的线程正在等待获取哪一个锁。在生成线程转储之前,JVM将在等待关系图通过循环来找出死锁。如果发现了一个死锁,则获取相应的死锁信息,例如在死锁中涉及哪些锁和线程,以及这个锁的获取操作位于程序的哪些位置。

  显示锁比在内置锁上获得的信息精确度低。内置锁与获得它们所在的线程栈帧是相关联的,而显式的Lock只与获得它的线程相关联。

三、其他活跃性危险

1、饥饿——线程由于无法访问它所需要的资源时而不能继续执行

  • 例:持有锁时执行一些无法结束的结构;底优先级的任务获取不到CPU资源
  • 要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级

2、糟糕的响应性

  • 例:后台任务若为CPU密集型,将可能影响程序响应性
  • 例:不良的锁管理,某个锁持有过长时间

3、活锁——线程将不断重复执行相同的操作,而且总会失败(不会阻塞线程,但也不能继续执行)

  • 例:错误的事务运行失败放在队列头
  • 例:多个相互协作的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程都无法继续执行(通过等待随机长度的时间和回退可以有效的避免活锁)

java并发编程实战:第十章----避免活跃性危险的更多相关文章

  1. java并发编程(3)避免活跃性危险

    活跃性危险 一.死锁 发生:每个人都不愿意放弃自己的锁,确想要别人的锁,这就会导致死锁  1.锁顺序死锁:如果每个线程以固定的顺序获取锁,那么至少在程序中不会出现锁顺序导致的死锁: 因为顺序固定如:所 ...

  2. Java并发编程阅读笔记-锁和活跃性问题

  3. Java并发编程实战 05等待-通知机制和活跃性问题

    Java并发编程系列 Java并发编程实战 01并发编程的Bug源头 Java并发编程实战 02Java如何解决可见性和有序性问题 Java并发编程实战 03互斥锁 解决原子性问题 Java并发编程实 ...

  4. 《Java并发编程实战》/童云兰译【PDF】下载

    <Java并发编程实战>/童云兰译[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062521 内容简介 本书深入浅出地介绍了Jav ...

  5. 《java并发编程实战》笔记

    <java并发编程实战>这本书配合并发编程网中的并发系列文章一起看,效果会好很多. 并发系列的文章链接为:  Java并发性和多线程介绍目录 建议: <java并发编程实战>第 ...

  6. Java并发编程实战.笔记十一(非阻塞同步机制)

    关于非阻塞算法CAS. 比较并交换CAS:CAS包含了3个操作数---需要读写的内存位置V,进行比较的值A和拟写入的新值B.当且仅当V的值等于A时,CAS才会通过原子的方式用新值B来更新V的值,否则不 ...

  7. Java并发编程实战——读后感

    未完待续. 阅读帮助 本文运用<如何阅读一本书>的学习方法进行学习. P15 表示对于书的第15页. Java并发编程实战简称为并发书或者该书之类的. 熟能生巧,不断地去理解,就像欣赏一部 ...

  8. 【Java并发编程实战】----- AQS(四):CLH同步队列

    在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...

  9. 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport

    在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...

  10. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

随机推荐

  1. redis Linux 、Windows ubuntu 下的安装

    Redis 安装 2018-07-05 Window 下安装 下载地址:https://github.com/MSOpenTech/redis/releases. Redis 支持 32 位和 64 ...

  2. vim配置之安装脚本

    vimConfig/install/install.sh git clone https://github.com/gmarik/vundle.git ~/.vim/bundle/vundle cp ...

  3. java.lang.NumberFormatException:For input string:"undefined"

    在将字符串转换为数字时导致此错误,解决此问题的思路: 1.添加 try catch语句 2.判断字符串是否为数字,将介绍java中判断字符串是否为数字的方法的几种方法 发生错误的代码: java.la ...

  4. thinkPHP使用函数时字符串中不能含有管道符”|“,否则报错;

    如 {$data.name|str_repeat="|",###}报错!!!

  5. Hadoop Hive 中的排序 Order by ,Sort by ,Distribute by以及 Cluster By

    order by order by 会对输入做全局排序,因此只有一个reducer(多个reducer无法保证全局有序)只有一个reducer,会导致当输入规模较大时,需要较长的计算时间. set h ...

  6. Getting Started(入门)

    欢迎阅读专门针对android开发者的培训课程,在这一系列的课程中,描述了如何通过我们的示例代码来完成特定的任务和功能,这些代码可以灵活地应用到你的应用程序中. 课程被分成了几部分, 第一部分,入门, ...

  7. Windows 程序 dump 崩溃调试

    Windows 程序捕获崩溃异常 生成dump 概述 事情的起因是,有个同事开发的程序,交付的版本程序,会偶尔随机崩溃了. 悲催的是没有输出log,也没有输出dump文件. 我建议他给程序代码加个异常 ...

  8. Httpservlet源码说明

    上一篇看了Servlet接口,现在来看下我们经常涉及的Httpservlet: /** * * Provides an abstract class to be subclassed to creat ...

  9. We could not complete your iTunes Store request

    We could not complete your iTunes Store request.An unknown error occurred(502). There was an error i ...

  10. JSF多列预选择

    <script type="text/javascript"> jQuery(document).ready(function () { preSelect(); }) ...