避免同步死锁

翻译自  避免同步死锁

在我之前的文章“ Double-Checked Locking:Clever,but Broken ”(JavaWorld,2001年2月),我描述了几种常用的避免同步的技术实际上是不安全的,并建议了一个“如有疑问,同步”的策略。一般来说,只要您正在读取以前可能由另一个线程写入的变量,或者您正在编写可能随后被另一个线程读取的变量,就应该进行同步。此外,尽管同步带来了性能损失,但与无争用同步相关的惩罚并不如某些消息来源所暗示的那样严重,并且随着每个连续的JVM实现而不断降低。所以现在似乎没有什么理由避免同步了。然而,另一个风险与过度同步相关:死锁。

什么是僵局?

我们说,当每个线程正在等待只有集合中的另一个进程可能导致的事件时,一组进程或线程才会死锁另一种解释死锁的方法是构建一个有向图,其顶点是线程或进程,其边缘表示“正在等待”关系。如果此图形包含一个循环,则系统处于死锁状态。除非系统设计为从死锁中恢复,否则死锁会导致程序或系统挂起。

Java程序中的同步死锁

在Java中会发生死锁,因为synchronized关键字会导致执行线程在等待与指定对象关联的锁定或监视器时阻塞。由于线程可能已经拥有与其他对象关联的锁,因此两个线程都可以等待另一个线程释放锁; 在这种情况下,他们最终会永远等待。以下示例显示了一组可能发生死锁的方法。两种方法都获得两个锁对象的锁,cacheLock并且tableLock在它们继续之前。在这个例子中,充当锁的对象是全局(静态)变量,这是通过在较粗粒度级别执行锁定来简化应用程序锁定行为的常用技术:

清单1.潜在的同步死锁

 public static Object cacheLock = new Object();public static Object tableLock = new Object();...public void oneMethod() {synchronized (cacheLock) {synchronized (tableLock) { doSomething();}}}public void anotherMethod() {synchronized (tableLock) {synchronized (cacheLock) { doSomethingElse();}}}

现在,想象线程A oneMethod()在线程B同时调用时调用anotherMethod()想象一下,线程A获取锁定cacheLock,同时线程B获取锁定tableLock现在线程被锁死了:线程将不会放弃它的锁,直到它获得另一个锁,但是在另一个线程放弃它之前,它们都不能获取其他锁。当一个Java程序死锁时,死锁线程会永远等待。虽然其他线程可能会继续运行,但最终必须杀死程序并重新启动它,并希望它不会再次发生死锁。

测试死锁是困难的,因为死锁取决于时间,负载和环境,因此可能不经常发生或仅在某些情况下发生。代码可能具有死锁的可能性,如清单1所示,但是在发生随机事件和非随机事件的某些组合之前不会出现死锁,例如程序受到特定的加载级别,在某个硬件配置上运行或暴露于某个特定的用户操作和环境条件的混合。僵局类似于在我们的代码中等待爆炸的时间炸弹; 当他们这样做时,我们的程序就会挂起。

不一致的锁定排序会导致死锁

幸运的是,我们可以对锁定采集施加相对简单的要求,以防止同步死锁。清单1的方法有可能发生死锁,因为每个方法都以不同的顺序获取两个锁如果清单1已经被写入,以便每个方法以相同的顺序获取两个锁,那么执行这些方法的两个或更多个线程不会死锁,无论时序或其他外部因素如何,因为如果没有线程可以获取第二个锁,第一。如果你能保证始终以一致的顺序获取锁,那么你的程序不会死锁。

僵局并不总是那么明显

一旦适应锁定顺序的重要性,您可以轻松识别清单1的问题。但是,类似的问题可能不太明显:也许这两种方法驻留在不同的类中,或者可能通过调用synchronized方法隐式获取涉及的锁,而不是通过同步块显式获取。考虑这两个合作类,Model并且View在一个简化的MVC(模型 - 视图 - 控制器)框架中:

清单2.更微妙的潜在同步死锁

 public class Model { private View myView;public synchronized void updateModel(Object someArg) { doSomething(someArg);myView.somethingChanged();}public synchronized Object getSomething() { return someMethod();}}public class View { private Model underlyingModel;public synchronized void somethingChanged() { doSomething();      }public synchronized void updateView() { Object o = myModel.getSomething();}}

清单2有两个具有同步方法的合作对象; 每个对象调用其他的同步方法。这种情况类似于清单1 - 两种方法获取相同两个对象的锁定,但以不同的顺序。然而,这个例子中的不一致的锁定顺序比清单1中的要少得多,因为锁定获取是方法调用的一个隐含部分。如果一个线程Model.updateModel()在另一个线程同时调用View.updateView()同时调用,则第一个线程可以获得Model锁并等待该View锁,而另一个线程获得该View锁并永久等待该Model锁。

您可以更深入地埋葬同步僵局的可能性。考虑一下这个例子:你有一种将资金从一个账户转移到另一个账户的方法。您希望在执行传输之前在两个帐户上获取锁定,以确保传输是原子性的。考虑这个无害的实现:

清单3.一个更微妙的潜在同步死锁

  public void transferMoney(Account fromAccount, Account toAccount, DollarAmount amountToTransfer) { synchronized (fromAccount) {synchronized (toAccount) { if (fromAccount.hasSufficientBalance(amountToTransfer) { fromAccount.debit(amountToTransfer); toAccount.credit(amountToTransfer);}}}}

即使所有在两个或更多帐户上运行的方法都使用相同的顺序,清单3也包含与清单1和清单2相同的死锁问题的种子,但以更微妙的方式。考虑线程A执行时会发生什么:

  transferMoney(accountOne, accountTwo, amount);

而在同一时间,线程B执行:

  transferMoney accountTwo accountOne anotherAmount );

同样,这两个线程试图获得相同的两个锁,但顺序不同; 僵局风险仍然笼罩,但形式不太明显。

如何避免死锁

防止发生死锁的最佳方法之一是避免一次获取多个锁,这通常是实用的。但是,如果这不可行,您需要一种策略,确保您以一致的,定义的顺序获取多个锁。

根据您的程序使用锁定的方式,确保您使用一致的锁定顺序可能并不复杂。在一些程序中(例如清单1),可能参与多重锁定的所有关键锁都是从一小组单身锁对象中抽取的。在这种情况下,您可以在一组锁上定义锁定获取顺序,并确保您始终按顺序获取锁定。一旦定义了锁定顺序,它只需要记录良好,以鼓励整个程序的一致使用。

收缩同步块以避免多重锁定

在清单2中,问题变得更加复杂,因为通过调用同步方法的结果,隐式获取锁。通常可以通过将同步范围缩小为尽可能小的块来避免类似清单2的情况所导致的潜在死锁。是否Model.updateModel()真的需要按住Model锁,而它调用View.somethingChanged()它通常不会; 整个方法可能被同步为捷径,而不是因为整个方法需要同步。但是,如果在方法内用同步块较小的方法替换同步方法,则必须将此锁定行为记录为方法的Javadoc的一部分。呼叫者需要知道他们可以安全地调用该方法而无需外部同步。调用者还应该知道该方法的锁定行为,以便他们可以确保以一致的顺序获取锁定。

更复杂的锁定排序技术

在其他情况下,如清单3的银行帐户示例,应用固定订单规则的情况会变得更加复杂; 您需要定义适合锁定的对象集合的总排序,并使用此排序选择锁定采集序列。这听起来很混乱,但实际上很简单。清单4说明了该技术; 它使用一个数字帐号来诱导Account对象的排序(如果您需要锁定的对象缺少像帐号这样的自然身份属性,则可以使用该Object.identityHashCode()方法生成一个。)

清单4.使用一个顺序来以固定顺序获取锁

 public void transferMoney(Account fromAccount, Account toAccount, DollarAmount amountToTransfer) { Account firstLock, secondLock;if (fromAccount.accountNumber() == toAccount.accountNumber())throw new Exception("Cannot transfer from account to itself");else if (fromAccount.accountNumber() < toAccount.accountNumber()) {firstLock = fromAccount;secondLock = toAccount;}else {firstLock = toAccount;secondLock = fromAccount;}synchronized (firstLock) {synchronized (secondLock) { if (fromAccount.hasSufficientBalance(amountToTransfer) { fromAccount.debit(amountToTransfer); toAccount.credit(amountToTransfer);}}}}

现在在呼叫中指定账户的顺序transferMoney()无关紧要; 锁总是以相同的顺序获取。

最重要的部分:文档

任何锁定策略中的一个关键 - 但经常被忽略的因素是文档。不幸的是,即使在设计锁定策略时非常小心的情况下,通常花费更少的努力来记录它。如果您的程序使用一小组单身锁,则应尽可能清楚地记录您的锁定顺序假设,以便将来的维护人员可以满足锁定顺序要求。如果一个方法必须获得一个锁来执行它的功能,或者必须使用特定的锁来调用,那么该方法的Javadoc应该注意到这一点。这样,未来的开发人员就会知道,调用给定的方法可能需要获得锁定。

很少有程序或类库充分记录其锁定使用情况。至少,每种方法都应该记录它获取的锁,以及呼叫者是否必须持有锁才能安全地调用该方法。另外,类应记录它们是否或在什么条件下它们是线程安全的。

在设计时注重锁定行为

由于死锁通常不明显,不经常发生且不可预知,所以它们可能会在Java程序中造成严重问题。通过在设计时注意程序的锁定行为并定义何时以及如何获取多个锁的规则,可以显着降低死锁的可能性。请记住记录您的程序的锁定采集规则及其同步的使用; 花在记录简单锁定假设上的时间将会大大减少以后出现死锁和其他并发问题的可能性。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/328516.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员小测试:保守派 vs 自由派

最近&#xff0c;我在阅读 Steve Yegg 的文集《程序员的呐喊》。 这是一本非常有趣的书&#xff0c;里面甚至包含了一个小测试&#xff08;原文&#xff09;&#xff0c;区分一个程序员到底是保守派还是自由派。 下面一共有十个问题&#xff0c;每个问题都有 A 和 B 两个选项&a…

新闻发布项目——业务逻辑层(newsTbService)

package bdqn.newsManageServlet.Service;import java.util.List;import bdqn.newsManageServlet.entity.newsTb;/*** 新闻业务逻辑层的接口* author Administrator**/ public interface newsTbService {//分页查询public List<newsTb>getPagingNews(int pagesize,int pa…

django mysql 创建表_关于 django ORM 中,数据库建表方式的问题

本人以前是做客户端的&#xff0c;做后端没多久。一直有一个问题困扰我&#xff0c;正好现在手上有一个系统在做。系统逻辑是写一个爬虫在网上爬取一个网站的信息&#xff0c;经过加工处理后&#xff0c;使用 django 来展示和交互。表结构简化为三张表A:爬虫爬取得到的信息B:数…

遍历多叉树

https://www.jianshu.com/p/dee8284b2dc4 beg4 关注 2018.03.22 15:14* 字数 334 阅读 172评论 0喜欢 1 随便画一个树,写代码遍历它 OK,树的结构这么描述 public class TreeNode {private String name;private TreeNode parent;private List<TreeNode> children new …

tomcat与apache的面试题

转载自 tomcat与apache的面试题tomcat与apache有哪几种连接方式&#xff1f; Tomcat 与 Apache 有三种连接方式&#xff0c; 1、JK方式 这是最常见的方式。JK 是通过 AJP 协议与 Tomcat 服务器进行通讯的&#xff0c;Tomcat 默认的 AJP Connector 的端口是 8009。JK 本身提供…

使用 Exceptionless 作为 Log Server 搭配 NLog 记录系统日志

昨天的文章<免费开源分布式系统日志收集框架 Exceptionless>反响很大,今天推的一篇是续集,文章来自于宝岛台湾的MVP, 让 Exceptionless 建置 Log Server,性能不用担心,用的是大名点点的ELK组合. 前言 痾...久违的新文章&#xff0c;让我的眼角流下了蛋蛋忧伤的泪珠.... 今…

elementui下拉框选择图片_element ui下拉框如何实现默认选择?

为什么我这样写没反应呢运营商级别 <el-optionv-for"item in options1":label"item.label":value"item.value"></el-option>export default{data(){return{selected:,options1:[{value: 选项1,label: 省级运营商}, {value: 选项2,la…

程序员成长之路 java面试指导(作者说的极好要看) 静下心看

https://blog.csdn.net/weixin_41780944/article/details/79429769 从面试官的角度分享一些后端校招经验&#xff08;作者说的极好要看&#xff09; https://blog.csdn.net/qq_34337272/article/details/80875016 除了手写代码和计算机基础&#xff0c;剩下的方面都是可选项…

[MySQL] 二进制的应用场景

前言 MySQL 是一种常用的关系型数据库管理系统&#xff0c;广泛应用于各种软件和网站开发中。在 MySQL 中&#xff0c;数据以二进制文件的形式存储在硬盘上。这些二进制文件不仅可以提供数据的持久化存储&#xff0c;还可以在不同的应用场景中发挥重要作用。本文将介绍 MySQL …

各种面试题(二)

转载自 各种面试题(二)1、面向对象的特征有哪些方面? 封装&#xff1a;通常认为封装是把数据和操作数据的方法绑定起来&#xff0c;对数据的访问只能通过已定义的接口。 多态性&#xff1a;多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象…

亚马逊如何变成 SOA(面向服务的架构)?

上一篇文章&#xff0c;我摘录了《程序员的呐喊》。这本书有趣的内容太多&#xff0c;今天再摘录一段。 1、 亚马逊公司不仅是世界最大的网络书店&#xff0c;还是世界最大的云服务商。它是怎么实现从电商到云商的转变呢&#xff1f; 一切都是CEO杰夫贝索斯促成的&#xff0c;他…

新闻发布项目——业务逻辑层(commentService)

package bdqn.newsManageServlet.Service;import java.util.List;import bdqn.newsManageServlet.Dao.commentDao; import bdqn.newsManageServlet.Dao.Impl.commentDaoImpl; import bdqn.newsManageServlet.entity.comment;/*** 新闻评论的业务逻辑层* author Administrator**…

linux 安装mysql 8.0_Linux安装mysql 8.0的详细方法介绍(代码示例)

本篇文章给大家带来的内容是关于Linux安装mysql 8.0的详细方法介绍(代码示例)&#xff0c;有一定的参考价值&#xff0c;有需要的朋友可以参考一下&#xff0c;希望对你有所帮助。经过一番努力下载mysql文件&#xff0c;我们可以开始Mysql8.0的安装了。解压文件// 解压文件生成…

亚信科技笔试面试2019届

首先笔试 笔试超级难 jsp 数据库 java 很多要百度 记得多找几个小伙伴哦~ 面试 一面: 技术面 讲一下你的项目 根据你的项目提问 面试官&#xff1a; 有哪些排序算法 我:冒泡 面试官&#xff1a; 只有一个冒泡? 我: 还有选择吧(我怕手写 &am…

Xamarin支持微软HoloLens混合现实开发

今年年初&#xff0c;微软以4亿美元的价格收购移动开发工具Xamarin&#xff0c;Xamarin允许开发者编写跨平台应用&#xff0c;提供Android、iOS和Windows10上原生应用体验。微软收购Xamarin后&#xff0c;Xamarin变得更加开放&#xff0c;比如Visual Studio用户免费使用&#x…

mysql fpmmm_zabbix配fpmmm(mpm)数据传送不了问题解决

我们环境用zabbix mpm来监控mysql&#xff0c;不过最近官网已经不叫mpm了&#xff0c;而是叫fpmmm&#xff0c;理由为&#xff1a;fpmmm is the successor of mpm. mpm was renamed to fpmmm because of the naming conflict with the Apache Multi-Processing Modules (MPM).好…

各种面试题(一)

转载自 各种面试题(一)1.说说你对缓存的理解&#xff1f; 1.使用缓存的目的&#xff1a; 提高应用程序的性能&#xff0c;减少到数据库的访问次数 2.缓存的介质&#xff08;缓存的存储位置&#xff09; 内存&#xff1a;临时性存储空间 存取数据快 减少网络通信量 硬盘&#…

浙大中控笔试面试2019届

学校招聘会投递简历 过了几天 叫我去滨江面试 先笔试 没人监考 &#xff08;但是面试的时候会抽几道笔试题问你&#xff09; 面试官是三个 吓尿了 面试官&#xff1a; 我: 面试官&#xff1a; JDBC会吗 讲一下 我: DriverManager获取连接对象..…

新闻发布项目——业务逻辑层(categoryTBService)

package bdqn.newsManageServlet.Service;import java.util.List;import bdqn.newsManageServlet.entity.categoryTB; import bdqn.newsManageServlet.entity.newsTb;/*** 新闻类别的业务逻辑层的接口* author Administrator**/ public interface categoryTBService {// 删除,根…

python初学函数_Python初学者速收藏!面向Python初学者的知识-函数

什么是函数式编程&#xff1f;函数式编程是一种编码范例&#xff0c;其中我们定义要做什么而不是执行什么动作。这个想法最初是从数学中采用的&#xff0c;我们定义输入到函数中的输入以及我们期望从中获得的输出。 函数式编程很少依赖状态管理-这意味着无论输出多少&#xff0…