研究死鎖–第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.pswp.cn/news/370541.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/370541.shtml
英文地址,請注明出處:http://en.pswp.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;使得同樣的構建過程可以創建不同的表示主要作用: 在用戶不知道對象的建造過程和細節的情況下就可以直接創建復雜的對象如何使用: 用戶只需要給出指定復雜對象的類型和內容, 建造者模式負責按順序創建復雜對象…