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

一、死锁

定义:在线程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. "WannaCry"勒索病毒用户处置指南

    "WannaCry"勒索病毒用户处置指南   原文: http://mp.weixin.qq.com/s/ExsribKum9-AN1ToT10Zog    卡巴斯基,下载官网:h ...

  2. Bootstrap-CL:超大屏幕

    ylbtech-Bootstrap-CL:超大屏幕 1.返回顶部 1. Bootstrap 超大屏幕(Jumbotron) 本章将讲解 Bootstrap 支持的另一个特性,超大屏幕(Jumbotro ...

  3. 面试总结之C/C++

    source code https://github.com/haotang923/interview/blob/master/interview%20summary%20of%20C%20and%2 ...

  4. 1021 docker初识

    docker与虚拟机相比,没有虚拟化内核,转而使用宿主机的内核.因此docker更轻更快 docker缺点:后端兼容性测试需求.把软件安装在不同的操作系统上进行测试,观察软件运行是否良好. 不能用do ...

  5. 常见的加密和解密算法—BASE64

    一.BASE64加密和解密概述 Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一,Base64编码可用于在HTTP环境下传递较长的标识信息.例如,在Java Persistence系 ...

  6. 引用变量类型的加载顺序(类名+引用名=new +类名();)

    程序如下: 运行结果如下: 以上结果说明:同一个引用名称(可以把它当做变量的一种类型)可能指代不同的对象,依据同一个引用是否处于同一个初始化的层次,决定是否在完成: static Cup c1=new ...

  7. python数据库连接池基于DBUtils

    DBUtils模块的使用的两种方式 DBUtils是Python的一个用于实现数据库连接池的模块 安装 pip install DBUtils 1.使用姿势一(不建议此方法) 为每个线程 (资源占用过 ...

  8. springboot 邮件服务

    springboot仍然在狂速发展,才五个多月没有关注,现在看官网已经到1.5.3.RELEASE版本了.准备慢慢在写写springboot相关的文章,本篇文章使用springboot最新版本1.5. ...

  9. c++builder 字节 编码 转换大全 String TBytes byte

    System.SysUtils System::DynamicArray<System::WideChar> TCharArray System::TArray__1<System: ...

  10. tomcat没有发布maven项目依赖的本地jar包

    建立springMVC的maven项目,平时使用的jar包都是在pom.xml文件配置依赖关系, maven会自动从仓库中下载,这样使用tomcat部署发布都没有问题.但有时我们需要使用maven仓库 ...