更多內(nèi)容,歡迎關(guān)注微信公眾號:全菜工程師小輝~
Lock接口的實現(xiàn)類
ReentrantLock是實現(xiàn)了Lock接口的類,屬于獨享鎖,獨享鎖在同一時刻僅有一個線程可以進行訪問。Lock接口很簡單,實現(xiàn)了如下:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
lock()和unlock()
lock()和unlock()必須成對出現(xiàn)。一般來說,使用lock必須在try{}塊中進行,并且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發(fā)生。通常使用ReentrantLock來進行同步的話,是以下面這種形式去使用的:
ReentrantLock lock = ...;
lock.lock();
try{
//處理任務(wù)
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
tryLock()
tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲?。?,則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。
tryLock(long time, TimeUnit unit)
tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區(qū)別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之內(nèi)如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內(nèi)拿到了鎖,則返回true。
lockInterruptibly()
這是一個可中斷的獲取鎖方法。
如果沒有其他線程持有鎖,則當(dāng)前線程獲取到鎖,并為鎖計數(shù)加1,并且立即返回;如果當(dāng)前線程已經(jīng)持有鎖,則為鎖計數(shù)加1,并立即返回。
如果其他線程持有鎖,則當(dāng)前線程將休眠直到下面兩個事件中的一個發(fā)生:
- 當(dāng)前線程獲取到鎖
- 其他線程中斷當(dāng)前線程
簡而言之,lockInterruptibly()在線程等待鎖期間是可以被interrupt的,而lock()在多線程等待鎖期間是無視interrupt的。
實驗示例:
class MyThread {
public static void main(String[] args) throws InterruptedException {
final Lock lock = new ReentrantLock();
lock.lock();
Thread t1 = new Thread(() -> {
lock.lock(); //注釋這行把下面注釋打開再試試
// try {
// lock.lockInterruptibly();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + " interrupted.");
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
Thread.sleep(3000);
}
}
注意,當(dāng)一個線程獲取了鎖之后,是不會被interrupt()方法中斷的。因為單獨調(diào)用interrupt()方法不能中斷正在運行過程中的線程,只能中斷阻塞過程中的線程。
ReadWriteLock接口的實現(xiàn)類
ReadWriteLock也是一個接口,在它里面只定義了兩個方法:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing.
*/
Lock writeLock();
}
一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操作分開,分成2個鎖來分配給線程,從而使得多個線程可以同時進行讀操作。ReentrantReadWriteLock實現(xiàn)了ReadWriteLock接口。
線程進入讀鎖的前提條件:
- 沒有其他線程的寫鎖,
- 沒有寫請求或者有寫請求,但調(diào)用線程和持有鎖的線程是同一個
- 線程進入寫鎖的前提條件:
- 沒有其他線程的讀鎖
- 沒有其他線程的寫鎖
ReentrantReadWriteLock支持以下功能:
- 支持公平和非公平的獲取鎖的方式;
- 支持可重入。讀線程在獲取了讀鎖后還可以獲取讀鎖;寫線程在獲取了寫鎖之后既可以再次獲取寫鎖又可以獲取讀鎖;
- 還允許從寫入鎖降級為讀取鎖,其實現(xiàn)方式是:先獲取寫入鎖,然后獲取讀取鎖,最后釋放寫入鎖。但是,從讀取鎖升級到寫入鎖是不允許的;
- 讀取鎖和寫入鎖都支持鎖獲取期間的中斷;
- Condition支持。僅寫入鎖提供了一個 Conditon 實現(xiàn);讀取鎖不支持 Conditon ,readLock().newCondition() 會拋出 UnsupportedOperationException。
ReentrantReadWriteLock里面提供了很多豐富的方法,不過最主要的有兩個方法:readLock()和writeLock()用來獲取讀鎖和寫鎖。
使用實例:
public class Test {
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
final Test test = new Test();
new Thread(){
public void run() {
test.get(Thread.currentThread());
};
}.start();
new Thread(){
public void run() {
test.get(Thread.currentThread());
};
}.start();
}
// 如果將函數(shù)改成synchronized的,會降低多線程的執(zhí)行效率
public void get(Thread thread) {
rwl.readLock().lock();
try {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在進行讀操作");
}
System.out.println(thread.getName()+"讀操作完畢");
} finally {
rwl.readLock().unlock();
}
}
}
Lock/ReadWriteLock和synchronized的區(qū)別:
總結(jié)來說,Lock和synchronized有以下幾點不同:
- Lock是一個接口,而synchronized是JAVA中的關(guān)鍵字,synchronized是內(nèi)置的語言實現(xiàn)
- synchronized在發(fā)生異常時,會自動釋放線程占有的鎖,因此不會導(dǎo)致死鎖現(xiàn)象發(fā)生;而Lock在發(fā)生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用Lock時需要在finally塊中釋放鎖
- Lock可以讓等待鎖的線程響應(yīng)中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應(yīng)中斷
- 通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到
- Lock可以提高多個線程進行讀操作的效率
官方表示,他們更支持synchronize,在未來的版本中還有優(yōu)化余地,所以還是提倡在synchronized能實現(xiàn)需求的情況下,優(yōu)先考慮使用synchronized來進行同步。






