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

public class Taxi {
private final Dispatcher dispatcher;
private Point location, destination; public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
} public synchronized Point getLocation() {
return location;
} public synchronized void setLocation(Point location){
this.location = location;
if(location.equals(destination)){
dispatcher.notifyAvaliable(this);
}
}
} public class Dispatcher {
private final Set<Taxi> taxis;
private final Set<Taxi> avaliableTaxis; public Dispatcher() {
taxis = new HashSet<Taxi>();
avaliableTaxis = new HashSet<Taxi>();
} public synchronized void notifyAvaliable(Taxi taxi) {
avaliableTaxis.add(taxi);
} public synchronized Image getImage() {
Image image = new Image();
for (Taxi t : taxis) {
image.drawMarker(t.getLocation());
}
return image;
}
}

  尽管没有任何方法会显式的获取两个锁,但setLocation和getImage等方法的调用者都会获得两个锁。因为setLocation和notifyAvailable都是同步方法,因此调用setLocation的线程将首先获得Taxi的锁,然后获取Dispatcher的锁,同样调用getImage的线程将首先获取Dispatcher的锁,然后再获取每一个Taxi的锁,两个线程按照不同的顺序来获取两个锁,这时就有可能产生死锁。

  解决方案是开放调用(如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用),使同步代码块仅被用于保护那些涉及共享状态的操作,修改代码如下:

public class Taxi {
private final Dispatcher dispatcher;
private Point location, destination; public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
} public synchronized Point getLocation() {
return location;
} public synchronized void setLocation(Point location) {
boolean reachedLocation;
synchronized (this) {
this.location = location;
reachedLocation = location.equals(destination);
}
if (reachedLocation) {
dispatcher.notifyAvaliable(this);
}
}
} public class Dispatcher {
private final Set<Taxi> taxis;
private final Set<Taxi> avaliableTaxis; public Dispatcher() {
taxis = new HashSet<Taxi>();
avaliableTaxis = new HashSet<Taxi>();
} public synchronized void notifyAvaliable(Taxi taxi) {
avaliableTaxis.add(taxi);
} public Image getImage(){
Set<Taxi> copy;
synchronized (this){
copy = new HashSet<Taxi>();
}
Image image = new Image();
for(Taxi t: copy){
image.drawMarker(t.getLocation());
}
return image;
}
}

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

二、除死锁外,还有几种活跃性危险,简单了解下:

(1)饥饿:当线程由于无法访问它所需要的资源而不能继续执行时,就发生了“饥饿”。
      引发饥饿的最常见资源就是CPU始终周期。
(2)活锁:线程将不断重复执行某个消息,而且总会失败。
      活锁通常发生在处理事务消息的应用程序中:如果不能成功的处理某个消息,那么消息处理机制将回滚整个事务,并将它重新放到队列的开头。
      当多个相互协作的线程都对彼此进行响应从而修改各自的状态,并使得任何一个线程否无法继续执行时,就发生了活锁。
      解决活锁的方案是在重试机制中引入随机性。

三、 减少锁的竞争:

1、有两个因素会影响在锁上发生竞争的可能性: 锁的请求频率,以及每次持有该锁的时间。

2、有三种方式可以降低锁的竞争程度:
  1)减少锁的持有时间:实际情况中,仅当可以将一些“大量”的计算或阻塞操作从同步代码块中移出时,才应该考虑同步代码块的大小。
  2)降低锁的请求频率;
  3)使用带有协调机制的独占锁,这些机制允许更高的并发性。

JAVA并发编程学习笔记------协作对象之间发生的死锁的更多相关文章

  1. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  2. Java并发编程学习笔记 深入理解volatile关键字的作用

    引言:以前只是看过介绍volatile的文章,对其的理解也只是停留在理论的层面上,由于最近在项目当中用到了关于并发方面的技术,所以下定决心深入研究一下java并发方面的知识.网上关于volatile的 ...

  3. Java 并发编程学习笔记 理解CLH队列锁算法

    CLH算法实现 CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁.结点之间是通过隐形的链表相连,之所以叫隐形的链 ...

  4. Java并发编程学习笔记(一)——线程安全性

    主要概念:线程安全性.原子性.原子变量.原子操作.竟态条件.复合操作.加锁机制.重入.活跃性与性能. 1.当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变 ...

  5. JAVA并发编程学习笔记------基础构建模块

    一.并发容器:ConcurrentHashMap:1.分段锁机制: 任意数量的读取线程可以并发的访问map,执行读取操作的线程和执行写入操作的线程可以并发的访问Map,并且一定数量的写入线程可以并发的 ...

  6. JAVA并发编程学习笔记------对象的可见性及发布逸出

    一.非原子的64位操作: 当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值,这种安全性保证被称为最低安全性.最低安全性适用于绝大多数变量 ...

  7. [转]JAVA并发编程学习笔记之Unsafe类

    1.通过Unsafe类可以分配内存,可以释放内存:类中提供的3个本地方法allocateMemory.reallocateMemory.freeMemory分别用于分配内存,扩充内存和释放内存,与C语 ...

  8. JAVA并发编程学习笔记------多线程调优

    1. 多线程场景下尽量使用并发容器代替同步容器 (如ConcurrentHashMap代替同步且基于散列的Map, 遍历操作为主要操作的情况下用CopyOnWriteArrayList代替同步的Lis ...

  9. Java并发编程学习笔记(三)——对象的组合

    重要概念: 1.在设计线程安全类的过程中,需要包含以下三个基本要素: (1)找出构成对象状态的所有变量. (2)找出约束状态变量的不变性条件. (3)建立对象状态的并发访问管理策略. 2.

随机推荐

  1. [国嵌笔记][012][GCC程序编译]

    GCC特点 GCC(GUN C Compiler)是GUN推出的功能强大.性能优越的多平台编译器.其执行效率与一般编译器相比平均效率要高20%~30%. GCC基本用法 gcc [options] f ...

  2. [国嵌攻略][045-046][一跃进入C大门]

    [一跃进入C大门] 跳转方式 1.相对跳转:b或bl指令,通过计算两个地址之间的差值来给pc赋值相对跳转 2.绝对跳转:ldr指令,通过给pc直接赋值,完成绝对跳转 代码编写 1.在汇编代码中直接使用 ...

  3. Java的语言特点详解

    1)简单性:java从C++简化而来,设计者们把C++语言中许多可用的特征去掉了,这些特征是一般程序员很少使用的.java还剔除了C++操作符过载和指针操作. 2)面向对象:java是一个面向对象的语 ...

  4. 使用ng-options指令创建下拉框

    今天在学习AngularJs中使用ng-options指令创建下拉框时遇到点问题,这里总结一下. 其实,使用ng-options指令创建下拉框很简单,只需要绑定两个属性. ng-options指令用于 ...

  5. java常量池詳解

    一.相关概念 什么是常量用final修饰的成员变量表示常量,值一旦给定就无法改变!final修饰的变量有三种:静态变量.实例变量和局部变量,分别表示三种类型的常量. Class文件中的常量池在Clas ...

  6. 小白的Python之路 day5 configparser模块的特点和用法

    configparser模块的特点和用法 一.概述 主要用于生成和修改常见配置文件,当前模块的名称在 python 3.x 版本中变更为 configparser.在python2.x版本中为Conf ...

  7. Thinkphp 3.2中文章详情页的上一篇 下一篇文章功能

      额 简单2句话解释下 获取上一篇文章的原理,其实就是以当前文章的id为起点进行进行查询,例如id=5的文章 select * from article where (article_id<5 ...

  8. dede表前缀不定时,查询表#@__archives

    $query = "SELECT arc.*,tp.typedir,tp.typename,               tp.isdefault,tp.defaultname,tp.nam ...

  9. cuda纹理内存的使用

    CUDA纹理内存的访问速度比全局内存要快,因此处理图像数据时,使用纹理内存是一个提升性能的好方法. 贴一段自己写的简单的实现两幅图像加权和的代码,使用纹理内存实现. 输入:两幅图 lena, moon ...

  10. UltraEdit激活方法

    按照UltraEdit 并下载注册机后. 打开UltraEdit 弹出产品是使用 然后点击 输入注册码, 重点:​断开网络 点击激活,弹出 离线激活选项.​ 用户名​密码随意输入,打开注册机,把下面的 ...