您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java中ReentrantLock常見的坑有哪些”,在日常操作中,相信很多人在Java中ReentrantLock常見的坑有哪些問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java中ReentrantLock常見的坑有哪些”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
Lock 是一個頂級接口,它的所有方法如下圖所示:
它的子類列表如下:
我們通常會使用 ReentrantLock 來定義其實例,它們之間的關聯如下圖所示:
PS:Sync 是同步鎖的意思,FairSync 是公平鎖,NonfairSync 是非公平鎖。
學習任何一項技能都是先從使用開始的,所以我們也不例外,咱們先來看下 ReentrantLock 的基礎使用:
public class LockExample { // 創建鎖對象 private final ReentrantLock lock = new ReentrantLock(); public void method() { // 加鎖操作 lock.lock(); try { // 業務代碼...... } finally { // 釋放鎖 lock.unlock(); } } }
ReentrantLock 在創建之后,有兩個關鍵性的操作:
加鎖操作:lock()
釋放鎖操作:unlock()
很多人會認為(尤其是新手朋友),ReentrantLock 默認的實現是公平鎖,其實并非如此,ReentrantLock 默認情況下為非公平鎖(這主要是出于性能方面的考慮),
比如下面這段代碼:
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 創建鎖對象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { // 定義線程任務 Runnable runnable = new Runnable() { @Override public void run() { // 加鎖 lock.lock(); try { // 打印執行線程的名字 System.out.println("線程:" + Thread.currentThread().getName()); } finally { // 釋放鎖 lock.unlock(); } } }; // 創建多個線程 for (int i = 0; i < 10; i++) { new Thread(runnable).start(); } } }
以上程序的執行結果如下:
從上述執行的結果可以看出,ReentrantLock 默認情況下為非公平鎖。因為線程的名稱是根據創建的先后順序遞增的,所以如果是公平鎖,那么線程的執行應該是有序遞增的,但從上述的結果可以看出,線程的執行和打印是無序的,這說明 ReentrantLock 默認情況下為非公平鎖。
想要將 ReentrantLock 設置為公平鎖也很簡單,只需要在創建 ReentrantLock 時,設置一個 true 的構造參數就可以了,如下代碼所示:
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 創建鎖對象(公平鎖) private static final ReentrantLock lock = new ReentrantLock(true); public static void main(String[] args) { // 定義線程任務 Runnable runnable = new Runnable() { @Override public void run() { // 加鎖 lock.lock(); try { // 打印執行線程的名字 System.out.println("線程:" + Thread.currentThread().getName()); } finally { // 釋放鎖 lock.unlock(); } } }; // 創建多個線程 for (int i = 0; i < 10; i++) { new Thread(runnable).start(); } } }
以上程序的執行結果如下:
從上述結果可以看出,當我們顯式的給 ReentrantLock 設置了 true 的構造參數之后,ReentrantLock 就變成了公平鎖,線程獲取鎖的順序也變成有序的了。
其實從 ReentrantLock 的源碼我們也可以看出它究竟是公平鎖還是非公平鎖,ReentrantLock 部分源碼實現如下:
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
從上述源碼中可以看出,默認情況下 ReentrantLock 會創建一個非公平鎖,如果在創建時顯式的設置構造參數的值為 true 時,它就會創建一個公平鎖。
使用 ReentrantLock 時一定要記得釋放鎖,否則就會導致該鎖一直被占用,其他使用該鎖的線程則會永久的等待下去,所以我們在使用 ReentrantLock 時,一定要在 finally 中釋放鎖,這樣就可以保證鎖一定會被釋放。
反例
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 創建鎖對象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { // 加鎖操作 lock.lock(); System.out.println("Hello,ReentrantLock."); // 此處會報異常,導致鎖不能正常釋放 int number = 1 / 0; // 釋放鎖 lock.unlock(); System.out.println("鎖釋放成功!"); } }
以上程序的執行結果如下:
從上述結果可以看出,當出現異常時鎖未被正常釋放,這樣就會導致其他使用該鎖的線程永久的處于等待狀態。
正例
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 創建鎖對象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { // 加鎖操作 lock.lock(); try { System.out.println("Hello,ReentrantLock."); // 此處會報異常 int number = 1 / 0; } finally { // 釋放鎖 lock.unlock(); System.out.println("鎖釋放成功!"); } } }
以上程序的執行結果如下:
從上述結果可以看出,雖然方法中出現了異常情況,但并不影響 ReentrantLock 鎖的釋放操作,這樣其他使用此鎖的線程就可以正常獲取并運行了。
lock 操作的次數和 unlock 操作的次數必須一一對應,且不能出現一個鎖被釋放多次的情況,因為這樣就會導致程序報錯。
反例
一次 lock 對應了兩次 unlock 操作,導致程序報錯并終止執行,示例代碼如下:
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 創建鎖對象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { // 加鎖操作 lock.lock(); // 第一次釋放鎖 try { System.out.println("執行業務 1~"); // 業務代碼 1...... } finally { // 釋放鎖 lock.unlock(); System.out.println("鎖釋鎖"); } // 第二次釋放鎖 try { System.out.println("執行業務 2~"); // 業務代碼 2...... } finally { // 釋放鎖 lock.unlock(); System.out.println("鎖釋鎖"); } // 最后的打印操作 System.out.println("程序執行完成."); } }
以上程序的執行結果如下:
從上述結果可以看出,執行第 2 個 unlock 時,程序報錯并終止執行了,導致異常之后的代碼都未正常執行。
在使用 ReentrantLock 時,需要注意不要將加鎖操作放在 try 代碼中,這樣會導致未加鎖成功就執行了釋放鎖的操作,從而導致程序執行異常。
反例
import java.util.concurrent.locks.ReentrantLock; public class LockExample { // 創建鎖對象 private static final ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { try { // 此處異常 int num = 1 / 0; // 加鎖操作 lock.lock(); } finally { // 釋放鎖 lock.unlock(); System.out.println("鎖釋鎖"); } System.out.println("程序執行完成."); } }
以上程序的執行結果如下:
從上述結果可以看出,如果將加鎖操作放在 try 代碼中,可能會導致兩個問題:
未加鎖成功就執行了釋放鎖的操作,從而導致了新的異常;
釋放鎖的異常會覆蓋程序原有的異常,從而增加了排查問題的難度。
到此,關于“Java中ReentrantLock常見的坑有哪些”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。