多線程之Lock的基本介紹
基本介紹
java.util.concurrent.locks是java1.5之後出現的一種鎖實現方式,是一個接口。但是在這之前已經有一個同步機制的實現就是synchronized關鍵字,那為什麽還要再出現一個LOCK接口呢?最主要的原因就是為了彌補synchronized使用中的不足。
synchronized優缺點
優點
1.synchronized所不用手動釋放鎖,即便拋出異常jvm也是讓線程自動釋放鎖
2.當 JVM 用 synchronized 管理鎖定請求和釋放時,JVM 在生成線程轉儲時能夠包括鎖定信息。這些對調試非常有價值,因為它們能標識死鎖或者其他異常行為的來源
缺點
1.使用synchronized如果其中一個線程不釋放鎖,那麽其他需要獲取鎖的線程會一直等待下去,等待的線程不能中途中斷,直到使用完釋放或者出現異常jvm會讓線程自動釋放鎖
2.也無法通過投票得到鎖,如果不想等下去,也就沒法得到鎖
3.同步還要求鎖的釋放只能在與獲得鎖所在的堆棧幀相同的堆棧幀中進行,多數情況下,這沒問題(而且與異常處理交互得很好),但是,確實存在一些非塊結構的鎖定更合適的情況
4.如果多個線程都只是進行讀操作,所以當一個線程在進行讀操作時,其他線程只能等待無法進行讀操作,性能比較低
Lock的優缺點
優點
1.Lock可以讓等待鎖的線程響應中斷
2.通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到
3.Lock可以提高多個線程進行讀操作的效率
4.Lockhe和 Condition類結合可以實現特定條件的等待中斷
缺點
1.使用lock必須手動釋放鎖,並且一般要放到finally裏保證一定釋放
2. Lock 類只是普通的類,JVM 不知道具體哪個線程擁有 Lock 對象
鎖分類
公平鎖和非公平鎖
公平鎖:指多個線程按照申請鎖的順序來獲取鎖。
非公平鎖:指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能會造成饑餓現象。
對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的控制線程對鎖的獲取, 所以並沒有任何辦法使其變成公平鎖。
獨占鎖和共享鎖
可重入鎖
可重入鎖又名遞歸鎖,是指同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖。
自旋鎖
自旋鎖是指嘗試獲取鎖的線程不會阻塞,而是采用循環的方式嘗試獲取鎖。好處是減少上下文切換,缺點是一直占用CPU資源。
悲觀鎖和樂觀鎖
樂觀鎖/悲觀鎖不是指具體類型的鎖,而是看待並發的角度。
悲觀鎖認為存在很多並發更新操作,采取加鎖操作,如果不加鎖一定會有問題
樂觀鎖認為不存在很多的並發更新操作,不需要加鎖。數據庫中樂觀鎖的實現一般采用版本號,Java中可使用CAS實現樂觀鎖。
分段鎖
分段鎖是一種鎖的設計,並不是一種具體的鎖。對於ConcuttentHashMap就是通過分段鎖實現高效的並發操作。
鎖實現
LOCK接口
Lock是一個接口,裏面有6個方法我們介紹一下每個使用方法。
- lock():lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他線程獲取,則進行等待。
Lock lock = ...; lock.lock(); try{ //處理任務 }catch(Exception ex){ }finally{ lock.unlock(); //釋放鎖 }
- tryLock():有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。
Lock lock = ...; if(lock.tryLock()) { try{ //處理任務 }catch(Exception ex){ }finally{ lock.unlock(); //釋放鎖 } }else { //如果不能獲取鎖,則直接做其他事情 }
- tryLock(long time, TimeUnit unit):和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。
- lockInterruptibly():當通過這個方法去獲取鎖時,如果線程正在等待獲取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀態。也就使說,當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有在等待,那麽對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。
public void method() throws InterruptedException { lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); } }
- unlock():釋放鎖,一般需要加鎖的資源(臨界區)需要放到try中,而unlock()要放到finally中
- newCondition():
ReentrantLock實現類
ReadWriteLock接口
ReentrantReadWriteLock實現類
Condition接口介紹
我們通過之前的學習知道了:synchronized關鍵字與wait()和notify/notifyAll()方法相結合可以實現等待/通知機制,ReentrantLock類當然也可以實現,但是需要借助於Condition接口與newCondition() 方法。Condition是JDK1.5之後才有的,它具有很好的靈活性,比如可以實現多路通知功能也就是在一個Lock對象中可以創建多個Condition實例(即對象監視器),線程對象可以註冊在指定的Condition中,從而可以有選擇性的進行線程通知,在調度線程上更加靈活。
在使用notify/notifyAll()方法進行通知時,被通知的線程是有JVM選擇的,使用ReentrantLock類結合Condition實例可以實現“選擇性通知”,這個功能非常重要,而且是Condition接口默認提供的。
而synchronized關鍵字就相當於整個Lock對象中只有一個Condition實例,所有的線程都註冊在它一個身上。如果執行notifyAll()方法的話就會通知所有處於等待狀態的線程這樣會造成很大的效率問題,而Condition實例的signalAll()方法 只會喚醒註冊在該Condition實例中所有等待線程
方法:
void await() | 相當於Object類的wait方法 |
boolean await(long time, TimeUnit unit) | 相當於Object類的wait(long timeout)方法 |
signal() | 相當於Object類的notify方法 |
signalAll() | 相當於Object類的notifyAll方法 |
conditon使用示例:
在使用wait/notify實現等待通知機制的時候我們知道必須執行完notify()方法所在的synchronized代碼塊後才釋放鎖。在這裏也差不多,必須執行完signal所在的try語句塊之後才釋放鎖,condition.await()後的語句才能被執行。
註意: 必須在condition.await()方法調用之前調用lock.lock()代碼獲得同步監視器,不然會報錯。
UseMoreConditionWaitNotify.java:
public class UseMoreConditionWaitNotify { public static void main(String[] args) throws InterruptedException { MyserviceMoreCondition service = new MyserviceMoreCondition(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); Thread.sleep(3000); service.signalAll_A(); } static public class ThreadA extends Thread { private MyserviceMoreCondition service; public ThreadA(MyserviceMoreCondition service) { super(); this.service = service; } @Override public void run() { service.awaitA(); } } static public class ThreadB extends Thread { private MyserviceMoreCondition service; public ThreadB(MyserviceMoreCondition service) { super(); this.service = service; } @Override public void run() { service.awaitB(); } } }
public class MyserviceMoreCondition { private Lock lock = new ReentrantLock(); public Condition conditionA = lock.newCondition(); public Condition conditionB = lock.newCondition(); public void awaitA() { lock.lock(); try { System.out.println("begin awaitA時間為" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionA.await(); System.out.println(" end awaitA時間為" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void awaitB() { lock.lock(); try { System.out.println("begin awaitB時間為" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionB.await(); System.out.println(" end awaitB時間為" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signalAll_A() { lock.lock(); try { System.out.println(" signalAll_A時間為" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionA.signalAll(); } finally { lock.unlock(); } } public void signalAll_B() { lock.lock(); try { System.out.println(" signalAll_B時間為" + System.currentTimeMillis() + " ThreadName=" + Thread.currentThread().getName()); conditionB.signalAll(); } finally { lock.unlock(); } } }
運行結果:
只有A線程被喚醒
LOCK和synchronized如何選擇
多線程之Lock的基本介紹