高并发编程-自定义带有超时功能的锁
文章目錄
- 概述
- 步驟
- 自定義超時異常處理類
- ILock接口
- 實現類
- 測試
- 存在的問題
- 修復存在的問題
- 超時功能
- 測試超時功能
- CustomLock
概述
我們知道synchronized的機制有一個很重要的特點是:使用synchronized, 當一個線程獲取了鎖,其他線程只能一直等待,等待這個獲取鎖的線程釋放鎖,如果這個線程執行時間很長,其他線程就需要一直等待 。 除非獲取鎖的線程執行完了該代碼塊,釋放鎖或者線程執行發生異常,JVM會使線程自動釋放鎖。
當然了J.U.C包中 Doug Lea大神已經設計了非常完美的解決方案,我們這里不討論J.U.C的實現。
我們自己實現一套的話,該如何實現呢? 有幾點需要思考
好了,開始吧
步驟
自定義超時異常處理類
既然要設計帶超時功能的鎖, 少不了當超時時,拋出異常,以便上層捕獲處理。
public class TimeOutException extends RuntimeException {public TimeOutException(String message){super(message);} }ILock接口
約定幾個接口方法: lock 、lock(long timeout)、unlock、getBlockedThread、getBlockedSize 詳見代碼注釋
package com.artisan.customLock;import java.util.Collection;public interface ILock {/*** 加鎖*/void lock() throws InterruptedException;/*** 加鎖* @param timeout 持有鎖的時間,過了該時間(毫秒) 自動釋放該鎖*/void lock(long timeout) throws InterruptedException,TimeOutException;/*** 釋放鎖*/void unlock();/*** 用于觀察 有哪些線程因沒有獲取到鎖被blocked* @return*/Collection<Thread> getBlockedThreads();/*** 被blocked的線程數量* @return*/int getBlockedSize();}實現類
詳見代碼注釋。 加鎖和釋放鎖方法 使用 synchronized 修飾,否則使用wait && notifyAll拋出異常
import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Optional;public class CustomLock implements ILock {// 默認false// true: 已經被線程搶到 false: 空閑private boolean lockFlag;// 用于存儲被blocked的線程,方便查看及計算被blocked的線程數量Collection<Thread> blockedThreadCollection = new ArrayList<>();/*** 構造函數中初始化該lockFlag*/public CustomLock(){this.lockFlag = false;}/*** synchronized 修飾該方法* @throws InterruptedException*/@Overridepublic synchronized void lock() throws InterruptedException {// 如果其他線程已經獲取到了鎖,讓該線程waitwhile(lockFlag){// 加入到blockedThreadCollectionblockedThreadCollection.add(Thread.currentThread());// waitthis.wait();}// 如果空閑,將該monitor置為trueblockedThreadCollection.remove(Thread.currentThread());lockFlag = true;}@Overridepublic void lock(long timeout) throws InterruptedException, TimeOutException {}@Overridepublic synchronized void unlock() {// 如果是加鎖的線程// 將Monitor置為空閑this.lockFlag = false;Optional.of(Thread.currentThread().getName() + " 釋放lock").ifPresent(System.out::println);// 喚醒其他正在等待的線程this.notifyAll();}@Overridepublic Collection<Thread> getBlockedThreads() {// blockedThreadCollection 可能被其他線程add 或者remove,這里定義為不可變的集合類型return Collections.unmodifiableCollection(blockedThreadCollection);}@Overridepublic int getBlockedSize() {return blockedThreadCollection.size();} }測試
package com.artisan.customLock;import java.time.LocalTime; import java.util.Optional; import java.util.stream.Stream;public class CustomLockTest {public static void main(String[] args) throws InterruptedException {CustomLock customLock = new CustomLock();// 開啟5個線程Stream.of("T1", "T2", "T3", "T4", "T5").forEach(name -> new Thread(() -> {// 加鎖 處理業務try {// 加鎖customLock.lock();Optional.of(Thread.currentThread().getName() + " hold the Monitor").ifPresent(System.out::println);// 調用業務work();} catch (InterruptedException e) {e.printStackTrace();}finally {// 在finally中釋放鎖customLock.unlock();}}, name).start());}/*** 模擬線程的業務邏輯** @throws InterruptedException*/public static void work() throws InterruptedException {Optional.of(Thread.currentThread().getName() +" begin to work " + LocalTime.now().withNano(0)).ifPresent(System.out::println);Thread.sleep(3_000);}}日志輸出:
"E:\Program Files\Java\jdk1.8.0_161\bin\java" "-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2017.2.4\lib\idea_rt.jar=53159:E:\Program Files\JetBrains\IntelliJ IDEA 2017.2.4\bin" -Dfile.encoding=UTF-8 -classpath "E:\Program Files\Java\jdk1.8.0_161\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\rt.jar;D:\IdeaProjects\mvc\target\classes" com.artisan.customLock.CustomLockTest T1 hold the Monitor T1 begin to work 22:19:14 T1 釋放lock T5 hold the Monitor T5 begin to work 22:19:17 T5 釋放lock T2 hold the Monitor T2 begin to work 22:19:20 T2 釋放lock T4 hold the Monitor T4 begin to work 22:19:23 T4 釋放lock T3 hold the Monitor T3 begin to work 22:19:26 T3 釋放lockProcess finished with exit code 0可以看到 確實是一個線程拿到鎖后,其他線程必須等待 。
針對第二點呢: 誰加的鎖,必須由誰來釋放 .
我們來測試下
存在的問題
針對第二點呢: 誰加的鎖,必須由誰來釋放 .
我們來測試下 : 假設我們在main線程中調用了unlock方法
重新運行測試,觀察日志
T1 hold the Monitor T1 begin to work 22:24:41 main 釋放lock T5 hold the Monitor T5 begin to work 22:24:41 T1 釋放lock T2 hold the Monitor T2 begin to work 22:24:44 T5 釋放lock T4 hold the Monitor T4 begin to work 22:24:44 T2 釋放lock T3 hold the Monitor T3 begin to work 22:24:47 T4 釋放lock T3 釋放lockProcess finished with exit code 0T1拿到鎖還沒有工作完,就被主線程釋放了,結果T5又搶到了… 很明顯不對了 。
修復存在的問題
見代碼
再次運行測試 ,OK
超時功能
@Overridepublic synchronized void lock(long timeout) throws InterruptedException, TimeOutException {// 入參不合理,直接調用lock ,也可拋出異常if (timeout <= 0 ) lock();// 線程等待的剩余時間long leftTime = timeout;// 計算結束時間long endTime = System.currentTimeMillis() + timeout;while(lockFlag){// 如果超時了,拋出異常if (leftTime <= 0){throw new TimeOutException(Thread.currentThread().getName() + " 超時...");}// 加入到blockedThreadCollectionblockedThreadCollection.add(Thread.currentThread());// wait 指定的時間this.wait(timeout);// 計算是否超時leftTime = endTime - System.currentTimeMillis();}// 如果空閑,將該monitor置為trueblockedThreadCollection.remove(Thread.currentThread());this.lockFlag = true;// 將當前線程置為lockHolderThreadthis.lockHolderThread = Thread.currentThread();}測試超時功能
package com.artisan.customLock;import java.time.LocalTime; import java.util.Optional; import java.util.stream.Stream;public class CustomLockTest {public static void main(String[] args) {CustomLock customLock = new CustomLock();// 開啟5個線程Stream.of("T1", "T2", "T3", "T4", "T5").forEach(name -> new Thread(() -> {// 加鎖 處理業務try {// 加鎖 最多等待100毫秒,如果100ms,沒搶到則中斷執行customLock.lock(100);Optional.of(Thread.currentThread().getName() + " hold the Monitor").ifPresent(System.out::println);// 調用業務work();} catch (InterruptedException e) {e.printStackTrace();} catch (TimeOutException e){Optional.of(Thread.currentThread().getName() + " timeOut").ifPresent(System.out::println);}finally {// 在finally中釋放鎖customLock.unlock();}}, name).start());}/*** 模擬線程的業務邏輯** @throws InterruptedException*/public static void work() throws InterruptedException {Optional.of(Thread.currentThread().getName() +" begin to work " + LocalTime.now().withNano(0)).ifPresent(System.out::println);Thread.sleep(3_000);}}運行結果:
OK。
CustomLock
package com.artisan.customLock;import java.time.LocalTime; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Optional;public class CustomLock implements ILock {// 默認false// true: 已經被線程搶到 false: 空閑private boolean lockFlag;// 用于存儲被blocked的線程,方便查看及計算被blocked的線程數量Collection<Thread> blockedThreadCollection = new ArrayList<>();// 當前持有鎖的線程Thread lockHolderThread ;/*** 構造函數中初始化該lockFlag*/public CustomLock(){this.lockFlag = false;}/*** synchronized 修飾該方法* @throws InterruptedException*/@Overridepublic synchronized void lock() throws InterruptedException {// 如果其他線程已經獲取到了鎖,讓該線程waitwhile(lockFlag){// 加入到blockedThreadCollectionblockedThreadCollection.add(Thread.currentThread());// waitthis.wait();}// 如果空閑,將該monitor置為trueblockedThreadCollection.remove(Thread.currentThread());this.lockFlag = true;// 將當前線程置為lockHolderThreadthis.lockHolderThread = Thread.currentThread();}@Overridepublic synchronized void lock(long timeout) throws InterruptedException, TimeOutException {// 入參不合理,直接調用lock ,也可拋出異常if (timeout <= 0 ) lock();// 線程等待的剩余時間long leftTime = timeout;// 計算結束時間long endTime = System.currentTimeMillis() + timeout;while(lockFlag){// 如果超時了,拋出異常if (leftTime <= 0){throw new TimeOutException(Thread.currentThread().getName() + " 超時...");}// 加入到blockedThreadCollectionblockedThreadCollection.add(Thread.currentThread());// wait 指定的時間this.wait(timeout);// 計算是否超時leftTime = endTime - System.currentTimeMillis();}// 如果空閑,將該monitor置為trueblockedThreadCollection.remove(Thread.currentThread());this.lockFlag = true;// 將當前線程置為lockHolderThreadthis.lockHolderThread = Thread.currentThread();}@Overridepublic synchronized void unlock() {// 如果是加鎖的線程if(lockHolderThread == Thread.currentThread()){// 將Monitor置為空閑this.lockFlag = false;Optional.of(Thread.currentThread().getName() + " 釋放lock" + LocalTime.now().withNano(0)).ifPresent(System.out::println);// 喚醒其他正在等待的線程this.notifyAll();}}@Overridepublic Collection<Thread> getBlockedThreads() {// blockedThreadCollection 可能被其他線程add 或者remove,這里定義為不可變的集合類型return Collections.unmodifiableCollection(blockedThreadCollection);}@Overridepublic int getBlockedSize() {return blockedThreadCollection.size();} } 《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的高并发编程-自定义带有超时功能的锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 高并发编程-线程生产者消费者的综合示例
- 下一篇: 高并发编程-Runtime.getRun