引導語
有的面試官喜歡讓同學在說完鎖的原理之後,讓你重寫一個新的鎖,要求現場在白板上寫出大概的思路和程式碼邏輯,這種面試題目,蠻難的,我個人覺得其重點主要是兩個部分:
考察一下你對鎖原理的理解是如何來的,如果你對源碼沒有解讀過的話,只是看看網上的文章,或者背面試題,也是能夠說出大概的原理,但你很難現場寫出一個鎖的實現代碼,除非你真的看過源碼,或有和鎖相關的專案經驗;
我們不需要創造,我們只需要模仿Java 鎖定中現有的API 進行重寫即可。
如果你看過原始碼,這題真的很簡單,你可以挑選一個你熟悉的鎖來模仿。
1、需求
一般自訂鎖的時候,我們都是根據需求來進行定義的,不可能憑空定義出鎖來,說到共享鎖,大家可能會想到很多場景,比如說對於共享資源的讀鎖可以是共享的,例如對於資料庫連結的共享訪問,例如對於Socket 服務端的連結數是可以共享的,場景有很多,我們選擇共享訪問資料庫連結這個場景來定義一個鎖。
2、詳細設計
假定(以下設想都為假定)我們的資料庫是單機mysql,只能承受10 個鏈接,在建立資料庫連結時,我們是透過最原始JDBC 的方式,我們用一個介面把用JDBC 建立連結的過程進行了封裝,這個介面我們命名為:建立連結介面。
共享存取資料庫連結的整體要求如下:所有請求加在一起的 mysql 連結數,最大不能超過 10(包含 10),一旦超過 10,直接報錯。
在這個背景下,我們進行了下圖的設計:
這個設計最關鍵的地方,就是我們透過能否獲得鎖,來決定是否可以獲得mysql 鏈接,如果能獲得鎖,那麼就能得到鏈接,否則直接報錯。
接著我們一起來看下落地的程式碼:
2.1、定義鎖定
首先我們需要定義一個鎖出來,定義時需要有兩個元素:
鎖的定義:同步器Sync;鎖對外提供的加鎖和解鎖的方法。
共享鎖定的程式碼實作如下:
// 共享不公平锁 public class ShareLock implements Serializable{ // 同步器 private final Sync sync; // 用于确保不能超过最大值 private final int maxCount; /** * 初始化时给同步器 sync 赋值 * count 代表可以获得共享锁的最大值 */ public ShareLock(int count) { this.sync = new Sync(count); maxCount = count; } /** * 获得锁 * @return true 表示成功获得锁,false 表示失败 */ public boolean lock(){ return sync.acquireByShared(1); } /** * 释放锁 * @return true 表示成功释放锁,false 表示失败 */ public boolean unLock(){ return sync.releaseShared(1); } }
從上述程式碼可以看出,加上鎖定和釋放鎖定的實現,都依賴同步器 Sync 的底層實作。
唯一要注意的是,鎖定需要規定好API 的規範,主要是兩方面:
API 需要什麼,就是鎖在初始化的時候,你需要傳哪些參數給我,在ShareLock 初始化時,需要傳最大可共享鎖定的數量;
#需要定義自身的能力,也就是定義每個方法的入參和出參。在ShareLock 的實作中,加鎖和釋放鎖的入參都沒有,是方法裡面寫死的1,表示每次方法執行,只能加鎖一次或釋放鎖一次,出參是布林值,true 表示加鎖或釋放鎖成功,false 表示失敗,底層使用的都是Sync 非公平鎖。
以上這種思考方式是有方法論的,就是我們在思考一個問題時,可以從兩個面向出發:API 是什麼? API 有什麼能力?
2.2、定義同步器Sync
Sync 直接繼承AQS ,程式碼如下:
class Sync extends AbstractQueuedSynchronizer { // 表示最多有 count 个共享锁可以获得 public Sync(int count) { setState(count); } // 获得 i 个锁 public boolean acquireByShared(int i) { // 自旋保证 CAS 一定可以成功 for(;;){ if(i<=0){ return false; } int state = getState(); // 如果没有锁可以获得,直接返回 false if(state <=0 ){ return false; } int expectState = state - i; // 如果要得到的锁不够了,直接返回 false if(expectState < 0 ){ return false; } // CAS 尝试得到锁,CAS 成功获得锁,失败继续 for 循环 if(compareAndSetState(state,expectState)){ return true; } } } // 释放 i 个锁 @Override protected boolean tryReleaseShared(int arg) { for(;;){ if(arg<=0){ return false; } int state = getState(); int expectState = state + arg; // 超过了 int 的最大值,或者 expectState 超过了我们的最大预期 if(expectState < 0 || expectState > maxCount){ log.error("state 超过预期,当前 state is {},计算出的 state is {}",state ,expectState); return false; } if(compareAndSetState(state, expectState)){ return true; } } } }
整個程式碼比較清晰,我們要注意的是:
邊界的判斷,例如入參是否非法,釋放鎖時,會不會出現預期的state 非法等邊界問題,對於此類問題我們都需要加以判斷,體現出思維的嚴謹性;
加鎖和釋放鎖,需要用for 自旋CAS 的形式,來確保當並發加鎖或釋放鎖時,可以重試成功。寫 for 自旋時,我們需要注意在適當的時機要 return,不要造成死循環,CAS 的方法 AQS 已經提供了,不要自己寫,我們自己寫的 CAS 方法是無法保證原子性的。
2.3、透過能否獲得鎖來決定能否得到連結
鎖定定義好了,我們需要把鎖和取得Mysql 連結結合起來,我們寫了一個Mysql 連結的工具類,稱為MysqlConnection,其主要負責兩大功能:
透過JDBC 建立和Mysql 的連結;
結合鎖,來防止請求過大時,Mysql 的總連結數不能超過10 個。
首先我們看下MysqlConnection 初始化的程式碼:
public class MysqlConnection { private final ShareLock lock; // maxConnectionSize 表示最大链接数 public MysqlConnection(int maxConnectionSize) { lock = new ShareLock(maxConnectionSize); } }
我們可以看到,在初始化時,需要製定最大的連結數是多少,然後把這個數值傳遞給鎖,因為最大的連結數就是ShareLock 鎖的state 值。
接著為了完成 1,我們寫了一個 private 的方法:
// 得到一个 mysql 链接,底层实现省略 private Connection getConnection(){}
然後我們實作 2,程式碼如下:
// 对外获取 mysql 链接的接口 // 这里不用try finally 的结构,获得锁实现底层不会有异常 // 即使出现未知异常,也无需释放锁 public Connection getLimitConnection() { if (lock.lock()) { return getConnection(); } return null; } // 对外释放 mysql 链接的接口 public boolean releaseLimitConnection() { return lock.unLock(); }
逻辑也比较简单,加锁时,如果获得了锁,就能返回 Mysql 的链接,释放锁时,在链接关闭成功之后,调用 releaseLimitConnection 方法即可,此方法会把锁的 state 状态加一,表示链接被释放了。
以上步骤,针对 Mysql 链接限制的场景锁就完成了。
3、测试
锁写好了,接着我们来测试一下,我们写了一个测试的 demo,代码如下:
public static void main(String[] args) { log.info("模仿开始获得 mysql 链接"); MysqlConnection mysqlConnection = new MysqlConnection(10); log.info("初始化 Mysql 链接最大只能获取 10 个"); for(int i =0 ;i<12;i++){ if(null != mysqlConnection.getLimitConnection()){ log.info("获得第{}个数据库链接成功",i+1); }else { log.info("获得第{}个数据库链接失败:数据库连接池已满",i+1); } } log.info("模仿开始释放 mysql 链接"); for(int i =0 ;i<12;i++){ if(mysqlConnection.releaseLimitConnection()){ log.info("释放第{}个数据库链接成功",i+1); }else { log.info("释放第{}个数据库链接失败",i+1); } } log.info("模仿结束"); }
以上代码逻辑如下:
获得 Mysql 链接逻辑:for 循环获取链接,1~10 都可以获得链接,11~12 获取不到链接,因为链接被用完了;释放锁逻辑:for 循环释放链接,1~10 都可以释放成功,11~12 释放失败。
我们看下运行结果,如下图:
从运行的结果,可以看出,我们实现的 ShareLock 锁已经完成了 Mysql 链接共享的场景了。
以上是Java重寫鎖的設計結構和細節是什麼的詳細內容。更多資訊請關注PHP中文網其他相關文章!

新興技術對Java的平台獨立性既有威脅也有增強。 1)雲計算和容器化技術如Docker增強了Java的平台獨立性,但需要優化以適應不同雲環境。 2)WebAssembly通過GraalVM編譯Java代碼,擴展了其平台獨立性,但需與其他語言競爭性能。

不同JVM實現都能提供平台獨立性,但表現略有不同。 1.OracleHotSpot和OpenJDKJVM在平台獨立性上表現相似,但OpenJDK可能需額外配置。 2.IBMJ9JVM在特定操作系統上表現優化。 3.GraalVM支持多語言,需額外配置。 4.AzulZingJVM需特定平台調整。

平台獨立性通過在多種操作系統上運行同一套代碼,降低開發成本和縮短開發時間。具體表現為:1.減少開發時間,只需維護一套代碼;2.降低維護成本,統一測試流程;3.快速迭代和團隊協作,簡化部署過程。

Java'splatformindependencefacilitatescodereusebyallowingbytecodetorunonanyplatformwithaJVM.1)Developerscanwritecodeonceforconsistentbehavioracrossplatforms.2)Maintenanceisreducedascodedoesn'tneedrewriting.3)Librariesandframeworkscanbesharedacrossproj

要解決Java應用程序中的平台特定問題,可以採取以下步驟:1.使用Java的System類查看系統屬性以了解運行環境。 2.利用File類或java.nio.file包處理文件路徑。 3.根據操作系統條件加載本地庫。 4.使用VisualVM或JProfiler優化跨平台性能。 5.通過Docker容器化確保測試環境與生產環境一致。 6.利用GitHubActions在多個平台上進行自動化測試。這些方法有助於有效地解決Java應用程序中的平台特定問題。

類加載器通過統一的類文件格式、動態加載、雙親委派模型和平台無關的字節碼,確保Java程序在不同平台上的一致性和兼容性,實現平台獨立性。

Java編譯器生成的代碼是平台無關的,但最終執行的代碼是平台特定的。 1.Java源代碼編譯成平台無關的字節碼。 2.JVM將字節碼轉換為特定平台的機器碼,確保跨平台運行但性能可能不同。

多線程在現代編程中重要,因為它能提高程序的響應性和資源利用率,並處理複雜的並發任務。 JVM通過線程映射、調度機制和同步鎖機制,在不同操作系統上確保多線程的一致性和高效性。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

EditPlus 中文破解版
體積小,語法高亮,不支援程式碼提示功能

ZendStudio 13.5.1 Mac
強大的PHP整合開發環境

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)