连续锁定2个不同的锁会死锁_研究死锁–第5部分:使用显式锁定

连续锁定2个不同的锁会死锁

在我的上一个博客中,我研究了使用Java的传统synchronized关键字和锁排序来修复破碎的,死锁的余额转移示例代码。 但是,有另一种方法称为显式锁定。

在这里,将锁定机制称为显式而非隐式的想法是, 显式表示它不是Java语言的一部分,并且已编写了一些类来实现锁定功能。 另一方面, 隐式锁定可以定义为语言的一部分,并且可以使用语言关键字synchronchized在后台实现锁定。

您可以争论明确锁定是否是一个好主意。 是否应该对Java语言进行改进,使其包括显式锁定功能,而不是向已经庞大的API中添加另一组类? 例如: trysynchronized()

显式锁定基于Lock接口及其ReentrantLock实现 。 与传统的synchronized关键字相比, Lock包含许多方法,这些方法可以使您对锁定进行更多控制。 它具有您期望的方法,例如lock()会在代码的受保护部分中创建入口点,而unlock()会在代码中创建出口点。 它还具有tryLock()tryLock(long time,TimeUnit unit) ,只有在有可用但尚未被另一个线程获取的情况下才会获取锁,而tryLock(long time,TimeUnit unit)则将尝试获取锁定,如果不可用则等待指定的计时器在放弃之前过期。

为了实现显式锁定,我首先向本系列以前的博客中使用的Account类添加了Lock接口。

public class Account implements Lock {private final int number;private int balance;private final ReentrantLock lock;public Account(int number, int openingBalance) {this.number = number;this.balance = openingBalance;this.lock = new ReentrantLock();}public void withDrawAmount(int amount) throws OverdrawnException {if (amount > balance) {throw new OverdrawnException();}balance -= amount;}public void deposit(int amount) {balance += amount;}public int getNumber() {return number;}public int getBalance() {return balance;}// ------- Lock interface implementation@Overridepublic void lock() {lock.lock();}@Overridepublic void lockInterruptibly() throws InterruptedException {lock.lockInterruptibly();}@Overridepublic Condition newCondition() {return lock.newCondition();}@Overridepublic boolean tryLock() {return lock.tryLock();}@Overridepublic boolean tryLock(long arg0, TimeUnit arg1) throws InterruptedException {return lock.tryLock(arg0, arg1);}@Overridepublic void unlock() {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}

在上面的代码中,您可以看到我通过封装一个ReentrantLock对象来支持聚合, Account类将锁定功能委托给该对象。 唯一需要注意的小型GOTCHAunlock()实现中:

@Overridepublic void unlock() {if (lock.isHeldByCurrentThread()) {lock.unlock();}}

它有一个附加的if()语句,该语句检查调用线程是否是当前持有锁的线程。 如果错过了这一行代码,那么您将得到以下IllegalMonitorStateException

Exception in thread 'Thread-7' java.lang.IllegalMonitorStateExceptionat java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)at threads.lock.Account.unlock(Account.java:76)at threads.lock.TrylockDemo$BadTransferOperation.transfer(TrylockDemo.java:98)at threads.lock.TrylockDemo$BadTransferOperation.run(TrylockDemo.java:67)

那么,这是如何实现的呢? 下面是基于我的原始DeadLockDemo程序的TryLockDemo示例的列表。

public class TrylockDemo {private static final int NUM_ACCOUNTS = 10;private static final int NUM_THREADS = 20;private static final int NUM_ITERATIONS = 100000;private static final int LOCK_ATTEMPTS = 10000;static final Random rnd = new Random();List<Account> accounts = new ArrayList<Account>();public static void main(String args[]) {TrylockDemo demo = new TrylockDemo();demo.setUp();demo.run();}void setUp() {for (int i = 0; i < NUM_ACCOUNTS; i++) {Account account = new Account(i, 1000);accounts.add(account);}}void run() {for (int i = 0; i < NUM_THREADS; i++) {new BadTransferOperation(i).start();}}class BadTransferOperation extends Thread {int threadNum;BadTransferOperation(int threadNum) {this.threadNum = threadNum;}@Overridepublic void run() {int transactionCount = 0;for (int i = 0; i < NUM_ITERATIONS; i++) {Account toAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));Account fromAccount = accounts.get(rnd.nextInt(NUM_ACCOUNTS));int amount = rnd.nextInt(1000);if (!toAccount.equals(fromAccount)) {boolean successfulTransfer = false;try {successfulTransfer = transfer(fromAccount, toAccount, amount);} catch (OverdrawnException e) {successfulTransfer = true;}if (successfulTransfer) {transactionCount++;}}}System.out.println("Thread Complete: " + threadNum + " Successfully made " + transactionCount + " out of "+ NUM_ITERATIONS);}private boolean transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {boolean success = false;for (int i = 0; i < LOCK_ATTEMPTS; i++) {try {if (fromAccount.tryLock()) {try {if (toAccount.tryLock()) {success = true;fromAccount.withDrawAmount(transferAmount);toAccount.deposit(transferAmount);break;}} finally {toAccount.unlock();}}} finally {fromAccount.unlock();}}return success;}}
}

想法是一样的,我有一个银行帐户列表,我将随机选择两个帐户,并将一个随机数从一个帐户转移到另一个帐户。 事情的核心是我更新的transfer(...)方法,如下所示。

private boolean transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {boolean success = false;for (int i = 0; i < LOCK_ATTEMPTS; i++) {try {if (fromAccount.tryLock()) {try {if (toAccount.tryLock()) {success = true;fromAccount.withDrawAmount(transferAmount);toAccount.deposit(transferAmount);break;}} finally {toAccount.unlock();}}} finally {fromAccount.unlock();}}return success;}

这里的想法是我尝试锁定fromAccount然后锁定toAccount 。 如果可行,那么我先进行转移,然后再记得解锁两个帐户。 如果那时帐户已经被锁定,那么我的tryLock()方法将失败,整个过程将循环并再次尝试。 尝试10000次锁定后,线程放弃并忽略传输。 我猜想在现实世界中的应用程序中,您希望将此故障放入某种队列中,以便以后进行调查。

在使用显式锁定时,您必须考虑它的工作原理,因此请看下面的结果…

Thread Complete: 17 Successfully made 58142 out of 100000
Thread Complete: 12 Successfully made 57627 out of 100000
Thread Complete: 9 Successfully made 57901 out of 100000
Thread Complete: 16 Successfully made 56754 out of 100000
Thread Complete: 3 Successfully made 56914 out of 100000
Thread Complete: 14 Successfully made 57048 out of 100000
Thread Complete: 8 Successfully made 56817 out of 100000
Thread Complete: 4 Successfully made 57134 out of 100000
Thread Complete: 15 Successfully made 56636 out of 100000
Thread Complete: 19 Successfully made 56399 out of 100000
Thread Complete: 2 Successfully made 56603 out of 100000
Thread Complete: 13 Successfully made 56889 out of 100000
Thread Complete: 0 Successfully made 56904 out of 100000
Thread Complete: 5 Successfully made 57119 out of 100000
Thread Complete: 7 Successfully made 56776 out of 100000
Thread Complete: 6 Successfully made 57076 out of 100000
Thread Complete: 10 Successfully made 56871 out of 100000
Thread Complete: 11 Successfully made 56863 out of 100000
Thread Complete: 18 Successfully made 56916 out of 100000
Thread Complete: 1 Successfully made 57304 out of 100000

这些表明,尽管该程序没有死锁并无限期地挂起,但它仅设法使余额中的转移只超过转移请求的一半。 这意味着它正在消耗大量的处理能力,包括循环,循环和循环-完全不是很有效。 另外,我刚才说过该程序“没有死锁并无限期地挂起”,这不是真的。 如果您考虑发生了什么,那么您会意识到程序陷入僵局,然后退出这种情况。

我的显式锁定演示代码的第二个版本使用上面提到的tryLock(long time,TimeUnit unit)

private boolean transfer(Account fromAccount, Account toAccount, int transferAmount) throws OverdrawnException {boolean success = false;try {if (fromAccount.tryLock(LOCK_TIMEOUT, TimeUnit.MILLISECONDS)) {try {if (toAccount.tryLock(LOCK_TIMEOUT, TimeUnit.MILLISECONDS)) {success = true;fromAccount.withDrawAmount(transferAmount);toAccount.deposit(transferAmount);}} finally {toAccount.unlock();}}} catch (InterruptedException e) {e.printStackTrace();} finally {fromAccount.unlock();}return success;}

在这段代码中,我用1毫秒的tryLock(...)超时替换了for循环。 这意味着,当tryLock(...)被调用并且无法获取锁定时,它将等待1 ms,然后回滚并放弃。

Thread Complete: 0 Successfully made 26637 out of 100000
Thread Complete: 14 Successfully made 26516 out of 100000
Thread Complete: 3 Successfully made 26552 out of 100000
Thread Complete: 11 Successfully made 26653 out of 100000
Thread Complete: 7 Successfully made 26399 out of 100000
Thread Complete: 1 Successfully made 26602 out of 100000
Thread Complete: 18 Successfully made 26606 out of 100000
Thread Complete: 17 Successfully made 26358 out of 100000
Thread Complete: 19 Successfully made 26407 out of 100000
Thread Complete: 16 Successfully made 26312 out of 100000
Thread Complete: 15 Successfully made 26449 out of 100000
Thread Complete: 5 Successfully made 26388 out of 100000
Thread Complete: 8 Successfully made 26613 out of 100000
Thread Complete: 2 Successfully made 26504 out of 100000
Thread Complete: 6 Successfully made 26420 out of 100000
Thread Complete: 4 Successfully made 26452 out of 100000
Thread Complete: 9 Successfully made 26287 out of 100000
Thread Complete: 12 Successfully made 26507 out of 100000
Thread Complete: 10 Successfully made 26660 out of 100000
Thread Complete: 13 Successfully made 26523 out of 100000

上面的结果表明,使用计时器时,余额转移成功率甚至下降到25%以上。 尽管现在还没有消耗大量的处理器时间,但效率仍然很低。

在相当长的一段时间内,我可能会花时间处理这两个代码示例,从而选择可以调整应用程序并提高性能的变量,但最终,没有什么真正的选择可以正确地进行锁排序。 我个人更愿意在可能的情况下使用老式的synchronized关键字隐式锁定,并为死锁代码过时,陈旧,难以理解的少数情况保留显式锁定,我已经尝试了其他所有方法,应用需要上线,已经很晚了,该回家了……

有关更多信息,请参阅本系列中的其他博客 。

该系列以及其他博客的所有源代码都可以在Github上找到,网址为git://github.com/roghughe/captaindebug.git

参考: 调查死锁–第5部分:使用来自Captain Debug博客博客的JCG合作伙伴 Roger Hughes的显式锁定 。

翻译自: https://www.javacodegeeks.com/2012/11/investigating-deadlocks-part-5-using-explicit-locking.html

连续锁定2个不同的锁会死锁

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

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

相关文章

fork source exec区别差异

fork 使用 fork 方式运行 script 时, 就是让 shell(parent process) 产生一个 child process 去执行该 script, 当 child process 结束后, 会返回 parent process, 但 parent process 的环境是不会因 child process 的改变而改变的. source 使用 source 方式运行 scrip…

LOJ2980 THUSC2017大魔法师(线段树+矩阵乘法)

线段树每个节点维护(A,B,C,len)向量&#xff0c;操作即是将其乘上一个矩阵。 #include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long lo…

在ECR上推送Spring Boot Docker映像

在先前的博客中&#xff0c;我们将Spring Boot应用程序与EC2集成在一起。 它是您可以在Amazon Web Services上进行的最原始的部署形式之一。 在本教程中&#xff0c;我们将使用我们的应用程序创建一个docker映像&#xff0c;该映像将存储到Amazon EC2容器注册表中 。 您需要安…

Linux信号处理机制

在Linux中&#xff0c;信号是进程间通讯的一种方式&#xff0c;它采用的是异步机制。当信号发送到某个进程中时&#xff0c;操作系统会中断该进程的正常流程&#xff0c;并进入相应的信号处理函数执行操作&#xff0c;完成后再回到中断的地方继续执行。 需要说明的是&#xff0…

pyspider爬虫框架

特点&#xff1a; 去重处理&#xff0c;结果监控&#xff0c;多进程处理&#xff0c;pyquery提取&#xff0c;错误重试&#xff0c;webUI管理&#xff0c;代码简洁&#xff0c;JS渲染 安装&#xff1a; anaconda里边没搜到pyspider&#xff0c;所以手动安装 查看pyspider的命令…

matlab subplot同时显示多幅图像

subplot是MATLAB中的函数。 使用方法&#xff1a;subplot&#xff08;m,n,p&#xff09;或者subplot&#xff08;m n p&#xff09;。 subplot是将多个图画到一个平面上的工具。其中&#xff0c;m表示是图排成m行&#xff0c;n表示图排成n列&#xff0c;也就是整个figure中有n个…

matlab var求方差

对二维数组a b1 var(a); % 按默认来求 b2 var(a, 0); % 默认的公式&#xff08;用N-1&#xff09; c1 var(a, 1); % 另外的公式&#xff08;用N&#xff09; d1 var(a, 0, 1); % 对每列操作&#xff08;用N-1&#xff09; d2 var(a, 0, 2); % 对每行操作&#xff08;…

1. git基础

1. 安装git sudo apt-get install git 2. 注册 git config --global user.name "Your Name" git config --global user.email "emailexample.com" 3. 创建版本库 mkdir learngit cd learngitgit init ##将这个目录初始化成…

国外persona用户画像_使用Mozilla Persona验证用户的指南

国外persona用户画像到目前为止&#xff0c;只有Twitter和Facebook身份验证&#xff0c;我决定将Mozilla Persona添加到我最新项目&#xff08; 计算机 &#xff0c;计算机生成的音乐&#xff09;的列表中。 为什么&#xff1f; 我喜欢尝试新事物 存储密码是一个艰巨的过程&a…

每天一个linux命令(41):ps命令

Linux中的ps命令是Process Status的缩写。ps命令用来列出系统中当前运行的那些进程。ps命令列出的是当前那些进程的快照&#xff0c;就是执行ps命令的那个时刻的那些进程&#xff0c;如果想要动态的显示进程信息&#xff0c;就可以使用top命令。 要对进程进行监测和控制&#x…

module.js:549 throw err;

解决方法&#xff1a; 1、有可能是拼写错误 2、未明原因。 &#xff08;1&#xff09;删除 node_modules 文件夹 &#xff08;2&#xff09;cnpm cache clean&#xff0c;不过提示错误就用 cnpm cache clean --force &#xff08;3&#xff09;cnpm install 如果还是显示错误就…

matlab zeros初始化为0矩阵

zeros为创建一个值为零的数组&#xff1b; 如matrix1zeros(4,5);%4*5的矩阵&#xff0c;矩阵中每个元素都为0 matrix2zeros(4,5,3);%4*5*3的数组&#xff0c;数组中每个元素都为0 下面举一个将图像存到数组的例子 对RGB图片1.jpg&#xff0c;2.jpg&#xff1b;大小为700*500…

在一个Java版本上运行Eclipse IDE,但在另一个Java版本上运行

Java™开发人员 &#xff08;和其他Java开发人员变体&#xff09;的Eclipse IDE本身就是用于构建Java应用程序的Java应用程序。 这种关系可能会让您的大脑有些奇怪。 Eclipse IDE几乎完全用Java编写&#xff0c;需要Java运行时环境&#xff08;JRE&#xff09;才能运行。 JRE仅…

1.6.4 分离原则: 策略同机制分离,接口同引擎分离

在Unix之失的讨论中&#xff0c;我们谈到过X系统的设计者在设计中的基本抉择是实行“机制&#xff0c;而不是策略”这种做法——使X成为一个通用图形引擎&#xff0c;而将用户界面风格留给工具包或者系统的其它层次来决定。这一点得以证明是正确的&#xff0c;因为策略和机制是…

十、LINQ查询之延迟执行

一、IEnumerable 来实现延迟加载 转载于:https://www.cnblogs.com/fger/p/10644549.html

matlab中“存储空间不足,无法处理此命令”

关于matlab中“存储空间不足&#xff0c;无法处理此命令” 前一段在matlab中绘制三维图片以及处理三维大数据时&#xff0c;电脑上出现了“存储空间不足&#xff0c;无法处理此命令”的提示&#xff0c;我便到网上查找一些相关的解决方案&#xff0c;但是很可惜&#xff0c;在…

inline用法详解

&#xff08;一&#xff09;inline函数&#xff08;摘自C Primer的第三版&#xff09; 在函数声明或定义中函数返回类型前加上关键字inline即把min&#xff08;&#xff09;指定为内联。 inline int min(int first, int secend) {/****/}; inline函数对编译器而言必须是可见的&…

matlab mat文件

如果想在matlab关闭后变量的值依然存在&#xff0c;就需要将变量保存在文件里&#xff0c;这里将变量保存为mat文件。 &#xff08;1&#xff09;将数组保存到mat文件 matrix1magic(4); save(matrix1.mat,matrix1); &#xff08;2&#xff09;需要使用mat文件时&#xff0c…

休眠日志:常见问题的提示和解决方案

如何通过适当的日志记录解决一些最常见的Hibernate问题&#xff1f; Hibernate的日志记录配置是一个重要但很少讨论的主题。 正确的配置可以帮助您在开发过程中发现潜在的问题&#xff0c;而错误的配置则可以在生产中导致严重的性能问题。 这就是我在新书《 Hibernate提示》中…