Lock 是頂級接口,它的所有方法如下圖:
##它的子類別清單如下:
我們通常會使用ReentrantLock 來定義其實例,它們之間的關聯如下圖所示:
PS:Sync 是同步鎖定的意思,FairSync 是公平鎖,NonfairSync 是非公平鎖。ReentrantLock 使用學習任何一項技能都是先從使用開始的,所以我們也不例外,咱們先來看下ReentrantLock 的基礎使用:
public class LockExample { // 创建锁对象 private final ReentrantLock lock = new ReentrantLock(); public void method() { // 加锁操作 lock.lock(); try { // 业务代码...... } finally { // 释放锁 lock.unlock(); } } }
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 時,它就會建立一個公平鎖。 2.在finally 中釋放鎖定
使用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 鎖的釋放操作,這樣其他使用此鎖的執行緒就可以正常取得並運行了。
3.鎖不能被釋放多次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常見的坑有哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!