跨资源死锁情形之1:客户端的增加导致资源池耗尽

  我们要介绍的第一种死锁情形是单纯由于负载而造成的,即资源池太小,而每个线程需要的资源超过了池中的可用资源。例如,考虑一个使用数据库连接的EJB调用,执行一个嵌套的EJB调用(使用同一连接池中不同的数据库连接)。例如,如果该嵌套的EJB调用声明为RequiresNew,就会出现死锁情形。

  在正常负载或者有足够大小的连接池的情况下,EJB调用将从池中获取一个数据库连接,然后调用嵌套的EJB。嵌套的EJB调用将从池中获取另一个数据库连接,提交内部事务,然后向池返回连接。外部EJB调用将提交自己的事务,并向池返回其连接。

  但是,假设连接池最多有10个连接,同时有10个对外部EJB的并发调用。这些线程中每一个都需要一个数据库连接用来清空池。现在,每个线程都执行嵌套的EJB调用(需要获取第二个数据库连接)。则所有线程都不能继续,但又都不放弃自己的第一个数据库连接。这样,10个线程都将被死锁。

  如果研究此类死锁情形,会发现线程转储中有大量等待获取资源的线程,以及同等数量的空闲且未阻塞的活动数据库连接。当应用程序死锁时,如果可以在运行时检测连接池,应该能确认连接池实际上已空。

  修复此类死锁的方法包括:增加连接池的大小或者重构代码,以便单个线程不需要同时使用很多数据库连接。如果单线程需要的最大数据库连接数为M,且可能的最大并发调用数为N,则要避免此问题,在池中所需的最小连接数为(N*(M01))+1。或者可以设置内部EJB调用以使用不同的连接池,即使外部调用的连接池为空,内部调用也能使用自己的连接池继续。

  跨资源死锁情形之2:单线程、多冲突数据库连接

  对同一线程执行嵌套的EJB调用时还会出现第二种跨资源死锁情形,此情形即使在非高负载系统中通常也会发生。同上面的示例一样,两个EJB调用使用不同的连接来连接到同一个数据库。因为只有嵌套调用完成后调用方才能继续,所以调用方的数据库连接实际上被嵌套调用的数据库连接阻塞了,虽然数据库没有注意到这种关系。如果第一个(外部)连接已获取第二个(内部)连接所需要的数据库锁,则第二个连接将永久阻塞第一个连接,并等待第一个连接被提交或回滚,这就出现了死锁情形。因为数据库没有注意到两个连接之间的关系,所以数据库不会将此情形检测为死锁。

  作为一个具体的示例,考虑一个数据加载EJB调用。此EJB调用获取一个大型对象,并在不同阶段中将其保存在数据库中。当它执行数据加载时,它会更新一个单独的表,以记录挂起数据加载操作的状态。我们希望状态更新立即可见,但不希望在未完成的状态下看到加载的数据,所以要通过调用“RequiresNew” EJB来完成。总的来说,这种不完善的数据加载方法如清单1中的代码所示。

  清单1

public void bulkLoadData(DataBatch batch) {
int batchId = batch.getId();
// Since this executeUpdate call doesn誸 happen in a separate
// transaction, it wouldn't be visible anyway, but the effect is
// far worse: a cross-resource deadlock.
executeUpdate("update batch_status set status='Started' " +
"where batch_id=" + batchId);
validateData(batch);
updateBatchStatus(batchId, "Validated"); // RequiresNew EJB call
loadDataStage1(batch);
updateBatchStatus(batchId, "Stage 1 complete"); // RequiresNew EJB call
loadDataStage2(batch);
updateBatchStatus(batchId, "Stage 2 complete"); // RequiresNew EJB call
finalizeDataLoad(batch);
updateBatchStatus(batchId, "Complete"); // RequiresNew EJB call
}

  在上面的示例中,使用updateBatchStatus方法执行“RequiresNew” EJB调用实际上可以更新batch_status数据库表,即使没有看到当前事务的效果,也能立即看到状态的改变。对executeUpdate的调用不是EJB调用,所以它和bulkLoadData的其他部分在同一个事务中执行。

  如上所述,即使不存在并发,此代码也将导致死锁。当bulkLoadData调用executeUpdate方法时,它更新现有的数据库行,这涉及为该行获取写锁。对updateBatchStatus的嵌套EJB调用将在单独的数据库连接上执行,并尝试执行一个非常相似的查询,但它将阻塞,因为不能获取必需的写锁。从数据库的角度来说,只要提交或回滚第一个连接的事务,第二个连接就可以继续。但是,Java虚拟机不允许在完成所有对updateBatchStatus的调用前完成bulkLoadD调用,这样就出现了死锁情形。

  该示例表明,一个更新会阻塞另一个更新,所以它会在任何数据库中导致死锁。如果初始更新查询是一个简单的选择查询,那么该示例仅在使用基于锁的并发控制的数据库上导致死锁,在这种数据库中,一个连接的读锁可以阻止另一个连接获取写锁。不管在哪种情况下,此类死锁即不依赖于同步,也不依赖于负载,而且线程转储将显示一个等待数据库响应的Java线程,但该线程与两个有效的数据库连接相关联。在这些数据库连接中,有一个将处于空闲状态,但会阻塞其他连接。

  此情形有多种具体的变种,可以涉及多个线程和两个以上的数据库连接。例如,外部EJB调用的数据库连接可能已经获取了数据库锁,该锁阻塞了另一个无关数据库连接的继续,但这个无关数据库连接已经获取了阻塞嵌套EJB调用的数据库操作的锁。这个特例是依赖于同步的,并将显示多个等待数据库响应的Java线程。其中至少有一个Java线程将与两个活动数据库连接相关联。

  跨资源死锁情形之3:Java虚拟机锁与数据库锁相冲突

  第三种死锁情形发生在数据库锁与Java虚拟机锁并存的时候。在这种情况下,一个线程占有一个数据库锁并尝试获取Java虚拟机锁(尝试进入同步的锁)。同时,另一个线程占有Java虚拟机锁并尝试获取数据库锁。再次地,数据库发现一个连接阻塞了另一个连接,但由于无法阻止连接继续,所以不会检测到死锁。Java虚拟机发现同步的锁中有一个线程,并有另一个尝试进入的线程,所以即使Java虚拟机能检测到死锁并对它们进行处理,它还是不会检测到这种情况。

  为了说明此种死锁情形,我们以一个简单的(不完善的)read-through cache为例。该cache是数据库表中备份的HashMap。如果出现缓存命中,它就从HashMap返回一个值。但在缓存缺失的情况下,它将从数据库读取值,将其添加到HashMap,然后返回该值,如清单2所示。

  清单 2

public class SimpleCache {
private Map cache = new HashMap();
public synchronized Object get(String key) {
if (cache.containsKey(key)) {
return cache.get(key);
} else {
Object value = queryForValue(key);
cache.put(key, value);
return value;
}
}
private Object queryForValue(String key) {
return executeQuery("select value from cache_table " +
"where key='" + key + "'");
}
public synchronized void clearCache() {
cache.clear();
}
// other methods omitted for brevity
}

  这是一个简单的遍历cache。注意:get()方法是同步的,这是因为我们访问了非线程安全容器,并要求containsKey/put组合在缓存缺失时是原子性的。

  该cache相当简单易懂:它约定,如果更改支持缓存的表中的数据,则应调用clearCache(),这样缓存就可以避免处理陈旧的数据。产生的缓存缺失将相应地重新进入缓存。

  我们现在来考虑可以更改此数据并清除缓存的代码:

public void updateData(String key, String value) {
executeUpdate("update cache_table set value='" + value +
"' where key='" + key + "'");
SimpleCache.getInstance().clearCache();
}

  上面的代码在简单的例子中能正常运行。但是,在使用基于锁的并发控制的数据库中,updateData中的查询将阻止queryForValue中的选择查询的执行,因为update语句将获取一个写锁,从而阻止选择查询获取同一数据行上的读锁。如果同步没有问题,一个线程可以尝试读取缓存中的给定值,并在另一个线程在数据库中更新该值时得到缓存缺失。如果数据库先执行update语句,它将阻塞select语句继续执行。但是,执行select语句的线程来自同步的get方法,所以它获取了SimpleCache上的锁。要返回updateData中的线程,它必须调用clearCache(),但不能获取锁(clearCache()是同步的)。

  当处理此情形的实例时,将有一个等待数据库响应的Java线程和一个等待获取Java虚拟机锁的线程。每个线程将与一个数据库连接相关联,其中一个连接阻塞另一个连接。修复方法是占有Java虚拟机锁时避免执行数据库操作,可以重写leCache的get()方法,如下所示:
  

public Object get(String key) {
synchronized(this) {
if (cache.containsKey(key)) {
return cache.get(key);
}
}
Object value = queryForValue(key);
synchronized(this) {
cache.put(key, value);
}
return value;
}

  既然现在我们知道了会发生此死锁情况,就可以使用Thread.holdsLock()向queryForValue方法添加检查以尝试避免死锁情况:

private Object queryForValue(String key) {
assert(!Thread.holdsLock(this));
return executeQuery(...);
}

  上例中的Thread.holdsLock()很有用,但是只有在我们知道需要留心哪个锁时它才会发挥作用。如果有一个类似的方法可以确定当前线程占有哪个Java虚拟机锁,那么会很有用。任何执行任何种类的RPC调用、数据库访问等的代码片段都可以抛出异常或记录警告,指示在占有Java虚拟机锁时执行这些操作会有危险。

  注意:虽然我们修复了上例中的死锁问题,但它仍有缺陷,因为在提交updateData的事务之前清空了缓存。如果在调用clearCache后、提交updateData事务前出现缓存缺失,则该缓存将加载旧数据,因为新数据尚未可见。这里的修复方法是仅在提交更改后清空缓存。注意,这只在MVCC数据库中发生。在基于锁的数据库中,挂起的update将阻塞缓存的读操作,所以在提交update的事务后缓存才能读取正确值。

转载于:https://www.cnblogs.com/xiefang1980/archive/2008/04/28/1174136.html

关于J2EE中死锁问题的研究(2)相关推荐

  1. DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子

    有了前面两节的基础,我们现在切入正题:研究下DllMain为什么会因为不当操作导致死锁的问题.首先我们看一段比较经典的"DllMain中死锁"代码.(转载请指明出于breaksof ...

  2. 论文中文翻译——Double-Fetch情况如何演变为Double-Fetch漏洞:Linux内核中的双重获取研究

    本论文相关内容 论文下载地址--Web Of Science 论文中文翻译--How Double-Fetch Situations turn into Double-Fetch Vulnerabil ...

  3. 导致DllMain中死锁的关键隐藏因子

    原文地址:https://blog.csdn.net/hczhiyue/article/details/18505087 有了前面两节的基础,我们现在切入正题:研究下DllMain为什么会因为不当操作 ...

  4. DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子2

    本文介绍使用Windbg去验证<DllMain中不当操作导致死锁问题的分析--导致DllMain中死锁的关键隐藏因子>中的结论,调试对象是文中刚开始那个例子.(转载请指明出于breakso ...

  5. 【博士论文】视觉语言交互中的视觉推理研究

    来源:专知 本文为论文,建议阅读5分钟 本文通过单轮交互和多轮交互两个场景,分别选取指称语理解和视觉对话两个代表性任务进行阐述. 来自中国人民大学牛玉磊的博士论文,入选2021年度"CCF优 ...

  6. 文章推荐 | 城市规划中城市信息学的研究进展

    来源:北京城市实验室BCL 随着计算机技术的飞速发展,城市信息学作为城市规划领域的一门新兴学科,逐渐引起学术界的关注.城市信息学的兴起给城市规划带来了新的压力,但它也提供了新的城市分析视角.在此背景下 ...

  7. 《中国人工智能学会通讯》——3.15 社交媒体中的谣言识别研究及其发展趋势...

    3.15 社交媒体中的谣言识别研究及其发展趋势 随着计算机和互联网技术的不断发展,社会已经进入了信息互联和人的互联高度融合的时代,人们可以在网络上自由地发布.传播和获取信息:人与人之间的联系也更加紧密 ...

  8. morlet包络检波matlab,布里渊光纤传感系统中的信号处理的研究

    布里渊光纤传感系统中的信号处理的研究2012年5月 TP247崔琳 毕卫红2012年5月 光学工程 TheResearchingonSignalProcessingMethodsofBrillouin ...

  9. Observer模式在J2EE中的实现

    引言: 设计模式是经验的文档化.它是对被用来在特定场景下解决一般设计问题的类和相互通信的对象的描述.更通俗的来说,它是一个问题/解决方案对.一旦我们掌握了设计模式,就等于拥有了一支强有力的专家队伍.它 ...

最新文章

  1. 如何对美工进行绩效考核?提升积极性?
  2. Linux awk 使用
  3. oracle 00980,ORA-00980如何解决
  4. python的史蒂芬加速迭代法_如何将Pandas迭代速度加快150倍?
  5. 为什么引入Memcached?
  6. c语言简单编程题模板,C语言编程题,比较简单
  7. 服务器网卡有什么作用,服务器网卡的作用
  8. yacc语法学习-part1
  9. 基于Java+SpringBoot+mybatis+vue+element实现旅游管理系统
  10. mysql数据恢复或数据找回方法
  11. mysql 3306端口入侵_3306端口入侵流程
  12. windows11错误代码0x0000011b怎么解决? 0x0000011b问题的相应解决办法
  13. 【送你一张门票】七牛云带你去看杭州云栖大会
  14. (M)Dynamic Programming:309. Best Time to Buy and Sell Stock with Cooldown
  15. Android之手机电池电量应用
  16. python中pymysql的小坑
  17. RhcsaLinux初次使用进行简单的操作
  18. mldonkey 的使用
  19. 屏幕测试图片全屏_全屏视频测试
  20. windows分屏设置鼠标左右

热门文章

  1. zkui:好用的zookeeper ui工具
  2. Linux 利用nginx源码编译安装nginx
  3. 菜鸟教程php多久学完,十天学会php(1)
  4. php atime,PHP DirectoryIterator getATime()用法及代码示例
  5. android gridview item 大小,关于GridView item动态宽度的问题
  6. python换行输入数据_python将回车作为输入内容的实例
  7. python中单下划线_foo与双下划线_Python中单下划线和双下划线
  8. mysql 5.0 php_PHP 5.0的新特性
  9. Pandas多层级索引的数据分析案例,超干货的!
  10. Python 3.10刚发布,这5点非常值得学习!