更多內容,歡迎關注微信公眾號:全菜工程師小輝~
Lock接口的實現類
ReentrantLock是實現了Lock接口的類,屬于獨享鎖,獨享鎖在同一時刻僅有一個線程可以進行訪問。Lock接口很簡單,實現了如下:
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()必須成對出現。一般來說,使用lock必須在try{}塊中進行,并且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。通常使用ReentrantLock來進行同步的話,是以下面這種形式去使用的:
ReentrantLock lock = ...; lock.lock(); try{ //處理任務 }catch(Exception ex){ }finally{ lock.unlock(); //釋放鎖 }
tryLock()
tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失?。存i已被其他線程獲?。?,則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。
tryLock(long time, TimeUnit unit)
tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。
lockInterruptibly()
這是一個可中斷的獲取鎖方法。
如果沒有其他線程持有鎖,則當前線程獲取到鎖,并為鎖計數加1,并且立即返回;如果當前線程已經持有鎖,則為鎖計數加1,并立即返回。
如果其他線程持有鎖,則當前線程將休眠直到下面兩個事件中的一個發生:
- 當前線程獲取到鎖
- 其他線程中斷當前線程
簡而言之,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); } }
注意,當一個線程獲取了鎖之后,是不會被interrupt()方法中斷的。因為單獨調用interrupt()方法不能中斷正在運行過程中的線程,只能中斷阻塞過程中的線程。
ReadWriteLock接口的實現類
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實現了ReadWriteLock接口。
線程進入讀鎖的前提條件:
- 沒有其他線程的寫鎖,
- 沒有寫請求或者有寫請求,但調用線程和持有鎖的線程是同一個
- 線程進入寫鎖的前提條件:
- 沒有其他線程的讀鎖
- 沒有其他線程的寫鎖
ReentrantReadWriteLock支持以下功能:
- 支持公平和非公平的獲取鎖的方式;
- 支持可重入。讀線程在獲取了讀鎖后還可以獲取讀鎖;寫線程在獲取了寫鎖之后既可以再次獲取寫鎖又可以獲取讀鎖;
- 還允許從寫入鎖降級為讀取鎖,其實現方式是:先獲取寫入鎖,然后獲取讀取鎖,最后釋放寫入鎖。但是,從讀取鎖升級到寫入鎖是不允許的;
- 讀取鎖和寫入鎖都支持鎖獲取期間的中斷;
- Condition支持。僅寫入鎖提供了一個 Conditon 實現;讀取鎖不支持 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(); } // 如果將函數改成synchronized的,會降低多線程的執行效率 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的區別:
總結來說,Lock和synchronized有以下幾點不同:
- Lock是一個接口,而synchronized是JAVA中的關鍵字,synchronized是內置的語言實現
- synchronized在發生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖
- Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷
- 通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到
- Lock可以提高多個線程進行讀操作的效率
官方表示,他們更支持synchronize,在未來的版本中還有優化余地,所以還是提倡在synchronized能實現需求的情況下,優先考慮使用synchronized來進行同步。