研究死锁–第5部分:使用显式锁定
在我的上一個博客中,我研究了使用Java的傳統(tǒng)synchronized關(guān)鍵字和鎖排序來修復(fù)破碎的,死鎖的余額轉(zhuǎn)移示例代碼。 但是,有一種替代方法稱為顯式鎖定。
這里,將鎖定機制稱為顯式而非隱式的想法是, 顯式表示它不是Java語言的一部分,并且已編寫了一些類來實現(xiàn)鎖定功能。 另一方面, 隱式鎖定可以定義為該語言的一部分,并且可以使用語言關(guān)鍵字synchronchized在后臺實現(xiàn)鎖定。
您可以爭論明確鎖定是否是一個好主意。 是否應(yīng)該對Java語言進行改進,使其包括顯式鎖定功能,而不是向已經(jīng)龐大的API中添加另一組類? 例如: trysynchronized() 。
顯式鎖定基于Lock接口及其ReentrantLock實現(xiàn) 。 與傳統(tǒng)的synchronized關(guān)鍵字相比, Lock包含許多方法,這些方法使您對鎖定有更多控制。 它具有您期望的方法,例如lock()會在代碼的受保護部分中創(chuàng)建入口點,而unlock()會在代碼中創(chuàng)建出口點。 它還具有tryLock()和tryLock(long time,TimeUnit unit)僅當(dāng)它可用且尚未被另一個線程獲取時才獲取tryLock() ,而tryLock(long time,TimeUnit unit)將嘗試獲取一個鎖,如果不可用則等待指定的計時器。在放棄之前過期。
為了實現(xiàn)顯式鎖定,我首先向本系列以前的博客中使用的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()實現(xiàn)中:
@Overridepublic void unlock() {if (lock.isHeldByCurrentThread()) {lock.unlock();}}它具有附加的if()語句,該語句檢查調(diào)用線程是否是當(dāng)前持有鎖的線程。 如果錯過了這一行代碼,那么您將獲得以下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)那么,這是如何實現(xiàn)的呢? 下面是基于我的原始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;}} }想法是一樣的,我有一個銀行帳戶列表,我將隨機選擇兩個帳戶,并從一個帳戶中隨機轉(zhuǎn)移一個金額。 問題的核心是我更新的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 。 如果可行,那么我先進行轉(zhuǎn)移,然后再記得解鎖兩個帳戶。 如果那時帳戶已經(jīng)被鎖定,那么我的tryLock()方法將失敗,整個過程將循環(huán)并再次嘗試。 嘗試10000次鎖定后,線程將放棄并忽略傳輸。 我猜想在現(xiàn)實世界的應(yīng)用程序中,您希望將此故障放入某種隊列中,以便以后進行調(diào)查。
在使用顯式鎖定時,您必須考慮它的工作原理,因此請看下面的結(jié)果…
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這些表明,盡管該程序沒有死鎖并無限期地掛起,但它僅設(shè)法使余額轉(zhuǎn)移只超過轉(zhuǎn)移請求的一半。 這意味著它正在消耗大量的處理能力,包括循環(huán),循環(huán)和循環(huán)-總體上不是很有效。 另外,我剛才說過該程序“沒有死鎖并無限期地掛起”,這不是真的。 如果您考慮發(fā)生了什么,那么您將意識到程序陷入僵局,然后退出這種情況。
我的顯式鎖定演示代碼的第二個版本使用上面提到的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循環(huán)。 這意味著,當(dāng)tryLock(...)被調(diào)用并且無法獲取鎖定時,它將等待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上面的結(jié)果表明,使用計時器時,余額轉(zhuǎn)移成功率甚至下降到25%以上。 盡管現(xiàn)在還沒有消耗大量的處理器時間,但效率仍然很低。
在相當(dāng)長的一段時間內(nèi),我可能會花時間處理這兩個代碼示例,從而選擇可以優(yōu)化應(yīng)用程序并提高性能的變量,但最終,沒有什么真正的選擇可以正確地進行鎖排序。 我個人更愿意在可能的情況下使用老式的synchronized關(guān)鍵字隱式鎖定,并為死鎖代碼過時,陳舊,難以理解的少數(shù)情況保留顯式鎖定,我已經(jīng)嘗試了其他所有方法,該應(yīng)用需要上線,已經(jīng)很晚了,該回家了……
有關(guān)更多信息,請參閱本系列中的其他博客 。
該系列以及其他博客的所有源代碼都可以在Github上找到,網(wǎng)址為git://github.com/roghughe/captaindebug.git
參考: 調(diào)查死鎖–第5部分:使用來自Captain Debug博客博客的JCG合作伙伴 Roger Hughes的顯式鎖定 。
翻譯自: https://www.javacodegeeks.com/2012/11/investigating-deadlocks-part-5-using-explicit-locking.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的研究死锁–第5部分:使用显式锁定的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 美国电信网络(美国电信网络 ddos)
- 下一篇: 安卓Gensoid K73(安卓get)