研究死锁–第5部分:使用显式锁定

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

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

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

显式锁定基于Lock接口及其ReentrantLock实现 。 与传统的synchronized关键字相比, Lock包含许多方法,这些方法使您对锁定有更多控制。 它具有您期望的方法,例如lock()会在代码的受保护部分中创建入口点,而unlock()会在代码中创建出口点。 它还具有tryLock()tryLock(long time,TimeUnit unit)仅当它可用且尚未被另一个线程获取时才获取tryLock() ,而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类将锁定功能委托给该对象。 唯一需要注意的小型GOTCHA是在unlock()实现中:

@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

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

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

相关文章

mysql 经典入门教程_MySQL 经典入门教程

MySQL 经典入门教程1 定义数据库中的表&#xff1a;一行叫一条记录。每一列叫一个属性&#xff0c;或一个字段。主键&#xff1a;表中的某个特殊字段&#xff0c;具有唯一的确定的值&#xff0c;可以根据该字段唯一的确定一条记录外键&#xff1a;表中的某个字段的值为另一张表…

druid连接池初始化慢_7、SpringBoot -连接池(Durid)

一导入相关核心包<dependencies>二 在application.ymlspring三、配置Druid Datasource(可选)Configuration五、监控访问 http://localhost:8080/druid&#xff0c; 使用上面配置的账号密码。四、自动配置原理源代码Configuration说明DataSourceProperties 配置相关 首先找…

负载均衡与反向代理

如果用域名 映射多了Ip &#xff1b; 外网应该用来实现 GSLB 1 轮询pstream nginxDemo { server 127.0.0.1:8081; server 127.0.0.1:8082; } 最少链接web请求会被转发到连接数最少的服务器上。 upstream nginxDemo { least_conn; server 127.0.…

使用工厂方法模式设计最佳实践

在前面的“设计模式”示例中&#xff0c;我们解释了当今常用的“工厂”模式。 在本节中&#xff0c;我们将了解具有更多抽象的更高级的解决方案。 该模式称为工厂方法设计模式。 定义&#xff1a; Factory方法模式提供了一种用于创建对象的方法&#xff0c;但是将对象创建委托…

偏导数

引入 一元函数导数&#xff1a; 在一元函数中&#xff0c;我们已经知道导数就是函数的变化率&#xff08;对于一个一元函数&#xff0c;x增大了多少&#xff0c;y增大了多少&#xff0c;这个就是变化率&#xff09;。对于二元函数我们同样要研究它的“变化率”。在xOy平面内&am…

qt绘制一圈圆_Qt绘制圆

最近开始折腾Qt了&#xff0c;手头上的一个项目需要用到Qt来绘制一些简单图像。记录下Qt绘制圆的过程&#xff1a;对于以A为圆心&#xff0c;半径为R的圆&#xff0c;外部有一个外切的正方形&#xff0c;正方形上有B点。如下图所示&#xff1a;对于void QPainter::drawArc(int …

前端基础之HTML

HTML介绍 Web服务本质 import socketsk socket.socket()sk.bind(("127.0.0.1", 8080)) sk.listen(5)while True:conn, addr sk.accept()data conn.recv(8096)conn.send(b"HTTP/1.1 200 OK\r\n\r\n")conn.secd(b"<h1>Hello world!</h1&g…

指令引用了 内存 该内存不能为read 一直弹窗_【翻译】使用Rust测试ARM和X86内存模型

原文标题: The Story of Tail Call Optimizations in Rust 原文标题: Examining ARM vs X86 Memory Models with Rust原文链接: https://www.nickwilcox.com/blog/arm_vs_x86_memory_model/公众号&#xff1a; Rust碎碎念苹果公司最近宣布&#xff0c;他们将要把笔记本和桌面电…

Docker应用二:docker常用命令介绍

Docker常用命令使用介绍 docker中常用的命令: 1、docker search image_name:搜查镜像 2、docker pull image_name:从镜像库中拉去镜像 3、docker run image_name:运行容器 --restartalways:容器退出后重新启动 --name:自定容器名字 --d:后台运行容器 --i:交互模式 --t:打开一个…

关于Ubuntu使用笔记

Ubuntu vm tools 安装 sudo apt install open-vm-tools-desktop 在安装程序时Ubuntu会将安装目录锁定&#xff0c;安装结束后会解除锁定&#xff0c;中断安装后无法再安装其他软件解决方案 E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavail…

具有可执行Tomcat的独立Web应用程序

在部署应用程序时&#xff0c;简单性是最大的优势。 您将了解到&#xff0c;尤其是在项目发展且需要在环境中进行某些更改时。 将整个应用程序打包到一个独立且自足的JAR中似乎是个好主意&#xff0c;尤其是与在目标环境中安装和升级Tomcat相比。 过去&#xff0c;我通常将Tomc…

css网页中设置背景图片的方法详解

在css代码中设置背景图片的方法&#xff0c;包括背景图片、背景重复、背景固定、背景定位等 用css设置网页中的背景图片&#xff0c;主要有如下几个属性&#xff1a; 1&#xff0c;背景颜色 {">说明&#xff1a;参数取值和颜色属性一样 注意&#xff1a;在HTML当中&am…

node-sass安装不成功的问题

SASS_BINARY_SITEhttps://npm.taobao.org/mirrors/node-sass/ npm install node-sass 简单粗暴的执行上述的命令。转载于:https://www.cnblogs.com/czaiz/p/6918114.html

npm升级依赖包_Taro跨端开发之依赖管理

昨天跑的好好项目,今天跑不起来我们在开发周期比较长的前端项目的时候,必然会遇到依赖管理的问题. 我们在开发项目的时候,我们用了大量的三方库.这些三方的依赖库时不时的会更新自己的代码.第三方依赖库的代码更新会很容易造成代码运行的不稳定, 比如昨天还跑的好好的项目,另一…

QOTD:Java线程与Java堆空间

以下问题很常见&#xff0c;并且与OutOfMemoryError有关&#xff1a;在JVM线程创建过程和JVM线程容量期间无法创建新的本机线程问题。 这也是我向新技术候选人&#xff08;高级职位&#xff09;提出的典型面试问题。 我建议您在查看答案之前尝试提供自己的答复。 题&#xff1…

sql查询重复项

select * from [表A] where id in (select id from [表A] group by id having count(id) >1 )转载于:https://www.cnblogs.com/wuyujie/p/7885017.html

java util logging_简单日志记录,使用java.util.logging

jspservletJavaBean模式下,可以做个简单的日志记录,日志文件保存在服务器.(Tomcat)package controller;import java.io.File;import java.io.IOException;import java.util.logging.FileHandler;import java.util.logging.Level;import java.util.logging.Logger;import javax.…

超级高铁

超级高铁 作者&#xff1a;武培&#xff0c;高培焱 作品来源&#xff1a;实践 美国电动汽车公司特斯拉和美国科技公司ET3都公布了“真空管钢运输”计划&#xff0c;特斯拉将其命名为“超级高铁”&#xff0c;ET3因列车外观酷似胶囊因而称之为“吃胶囊”列车。根据ET3公司的介绍…

使用Spring @Autowired List的责任链

在Spring 3.1中&#xff0c;有一种方法可以自动填充类型化的List&#xff0c;这在您想在代码中稍微进行去耦和清理时非常方便。 为了向您展示它是如何工作的&#xff0c;我将实现一个简单的责任链&#xff0c;该责任链将为通过的用户打印一些问候。 让我们从我们拥有的&#…

设计模式 建造者模式 与 Spring Bean建造者 BeanDefinitionBuilder 源码与应用

建造者模式 定义: 将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示主要作用: 在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象如何使用: 用户只需要给出指定复杂对象的类型和内容, 建造者模式负责按顺序创建复杂对象…