Lock接口Condition,以及Lock与synchronized异同
一、synchronized與Lock
基于synchronized關鍵字去實現同步的,(jvm內置鎖,也叫隱式鎖,由我們的jvm自動去加鎖跟解鎖的)juc下的基于Lock接口的這樣的一種鎖的實現方式(顯式鎖,他需要我們手動的去加鎖,跟解鎖)
一張圖了解一下區別:
二、Lock接口的實現
Lock鎖實現提供了比使用同步方法和語句可以獲得的更廣泛的鎖操作。它們允許更靈活的結構,可能具有非常不同的屬性,并且可能支持多個關聯的條件對象。
三、Lock實現ReentrantLock
由英文知道:ReentrantLock稱是重入鎖,可以反復得到相同的一把鎖,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數器就加1,然后鎖需要被釋放兩次才能獲得真正釋放(重入鎖)。圍繞著去實現的核心實質就是AQS(AbstractQueuedSynchronizer)(抽象隊列同步器)ReentrantLock和synchronized關鍵字一樣可以用來實現線程之間的同步互斥,但是在功能是比synchronized關鍵字更強大而且更靈活。
ReentrantLock類常見方法(Lock接口已有方法這里沒加上):
例子:
public class ReentrantLockTest extends Thread {public static ReentrantLock lock = new ReentrantLock();public static int i = 0;public ReentrantLockTest(String name) {super.setName(name);}@Overridepublic void run() {for (int j = 0; j < 100; j++) {lock.lock();try {System.out.println(this.getName() + " " + i);i++;} finally {lock.unlock();}}}/*** @param args* @throws InterruptedException*/public static void main(String[] args) throws InterruptedException {ReentrantLockTest test1 = new ReentrantLockTest("thread1");ReentrantLockTest test2 = new ReentrantLockTest("thread2");test1.start();test2.start();test1.join();test2.join();System.out.println(i);} } //以上例子,如果沒加lock會輸出一個小于200的數注意:ReentrantLock并不是一種替代內置加鎖的方法,而是作為一種可選擇的高級功能。相比于synchronized,ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時、公平鎖等特點。
以下是他和synchronized的對比:
- 1.synchronized是獨占鎖,加鎖和解鎖的過程自動進行,易于操作,但不夠靈活。ReentrantLock也是獨占鎖,加鎖和解鎖的過程需要手動進行,不易操作,但非常靈活。
- 2.synchronized可重入,因為加鎖和解鎖自動進行,不必擔心最后是否釋放鎖;ReentrantLock也可重入,但加鎖和解鎖需要手動進行(其實這也是Lock的相比synchronized的優點,更加靈活),且次數需一樣,否則其他線程無法獲得鎖。
- 3.synchronized不可響應中斷,一個線程獲取不到鎖就一直等著;ReentrantLock可以相應中斷。
- 4.ReentrantLock可設置超時退出,不會永久等待構成死鎖。
在synchronized和reentrantLock之間進行選擇:
雖然reentrantLock在1.5和1.6的表現中,性能遠好于synchronized,但還是提倡使用synchronized:
- 1.reentrantLock忘記關鎖的話,很危險
- 2.未來更可能提升的是 synchronized的性能,而不是reentrantLock
- 3.當需要用到某些高級性能時,才考慮reentrantLock,例如:可定時的,可輪詢的,可中斷的,公平隊列等
公平鎖與非公平鎖(AQS的原因)
AQS內部維護著一個FIFO的隊列,即CLH隊列。AQS的同步機制就是依靠CLH隊列實現的。
- 一般意義上的鎖是不公平的,不一定先來的線程能先得到鎖,后來的線程就后得到鎖。不公平的鎖可能會產生饑餓現象。
- 公平鎖的意思就是,這個鎖能保證線程是先來的先得到鎖。雖然公平鎖不會產生饑餓現象,但是公平鎖的性能會比非公平鎖差很多。
公平鎖:
非公平鎖:
注意: 默認情況下ReentranLock類使用的是非公平鎖,若要使用公平鎖,如下 :
四、Condition接口
我們知道synchronized關鍵字與wait()和notify/notifyAll()方法相配合就可以實現等待/通知機制,借助于Condition接口與newCondition() 方法ReentrantLock類也可以實現。Condition是JDK1.5之后才有的,它具有很好的靈活性,比如可以實現多路通知功能也就是在一個Lock對象中可以創建多個Condition實例(即對象監視器),線程對象可以注冊在指定的Condition中,從而可以有選擇性的進行線程通知,在調度線程上更加靈活。 condition可以通俗的理解為條件隊列。當一個線程在調用了await方法以后,直到線程等待的某個條件為真的時候才會被喚醒。這種方式為線程提供了更加簡單的等待/通知模式。Condition必須要配合鎖一起使用,因為對共享狀態變量的訪問發生在多線程環境下。一個Condition的實例必須與一個Lock綁定,因此Condition一般都是作為Lock的內部實現。
在使用notify/notifyAll()方法進行通知時,被通知的線程是由JVM選擇的,使用ReentrantLock類結合Condition實例可以實現“選擇性通知”,這個功能非常重要,而且是Condition接口默認提供的。而synchronized關鍵字就相當于整個Lock對象中只有一個Condition實例,所有的線程都注冊在它一個身上。如果執行notifyAll()方法的話就會通知所有處于等待狀態的線程這樣會造成很大的效率問題,而Condition實例的signalAll()方法 只會喚醒注冊在該Condition實例中的所有等待線程。
簡單地說:一個線程運行完之后通過condition.signal()或者condition.signalAll()方法通知下一個特定的運行,就這樣循環往復即可。
五、ReadWriteLock接口的實現類:ReentrantReadWriteLock
ReentrantLock(排他鎖)具有完全互斥排他的效果,即同一時刻只允許一個線程訪問,這樣做雖然雖然保證了實例變量的線程安全性,但效率非常低下。ReadWriteLock接口的實現類ReentrantReadWriteLock讀寫鎖就是為了解決這個問題。讀寫鎖維護了兩個鎖,一個是讀操作相關的鎖也成為共享鎖,一個是寫操作相關的鎖也稱為排他鎖。通過分離讀鎖和寫鎖,其并發性比一般排他鎖有了很大提升。多個讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥(只要出現寫操作的過程就是互斥的。)。在沒有線程Thread進行寫入操作時,進行讀取操作的多個Thread都可以獲取讀鎖,而進行寫入操作的Thread只有在獲取寫鎖后才能進行寫入操作。即多個Thread可以同時進行讀取操作,但是同一時刻只允許一個Thread進行寫入操作。
ReentrantReadWriteLock的特性:
ReentrantReadWriteLock常見方法: 構造方法
問題例子:
class MyCache{private volatile Map<String,Object> map = new HashMap<>();public void put(String key,Object value){System.out.println(Thread.currentThread().getName()+"\t 正在寫"+key);//暫停一會兒線程try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace(); }map.put(key,value);System.out.println(Thread.currentThread().getName()+"\t 寫完了"+key);}public Object get(String key){Object result = null;System.out.println(Thread.currentThread().getName()+"\t 正在讀"+key);try {TimeUnit.MILLISECONDS.sleep(300);} catch (InterruptedException e) {e.printStackTrace(); }result = map.get(key);System.out.println(Thread.currentThread().getName()+"\t 讀完了"+result);return result;} } public class ReadWriteLockDemo {public static void main(String[] args) {MyCache myCache = new MyCache();for (int i = 1; i <= 3; i++) {final int num = i;new Thread(()->{myCache.put(num+"",num+"");},String.valueOf(i)).start();}for (int i = 1; i <= 3; i++) {final int num = i;new Thread(()->{myCache.get(num+"");},String.valueOf(i)).start();}} } //結果(還沒寫完就重新再寫一個) 1 正在寫1 3 正在寫3 2 正在寫2 1 正在讀1 2 正在讀2 3 正在讀3 2 寫完了2 3 讀完了null 2 讀完了null 1 讀完了null 1 寫完了1 3 寫完了3完善代碼:
class MyCache{private volatile Map<String,String> map = new HashMap<>();private ReadWriteLock rwLock = new ReentrantReadWriteLock();public void put(String key,String value){rwLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName()+"\t 準備寫入數據"+key);TimeUnit.MILLISECONDS.sleep(200);map.put(key, value);System.out.println(Thread.currentThread().getName()+"\t 寫入數據完成"+key);} catch (Exception e) {e.printStackTrace();} finally {rwLock.writeLock().unlock();}}public void get(String key){rwLock.readLock().lock();try {System.out.println(Thread.currentThread().getName()+"\t 準備讀取數據"+key);TimeUnit.MILLISECONDS.sleep(200);String value = map.get(key);System.out.println(Thread.currentThread().getName()+"\t 讀取數據完成"+value);} catch (Exception e) {e.printStackTrace();} finally {rwLock.readLock().unlock();}} } public class ReadWriteLockDemo {public static void main(String[] args) throws InterruptedException {MyCache myCache = new MyCache();for (int i = 1; i <=3 ; i++) {String key = String.valueOf(i);new Thread(()->{myCache.put(key,UUID.randomUUID().toString().substring(0,8));},String.valueOf(i)).start();}TimeUnit.SECONDS.sleep(2);for (int i = 1; i <=3 ; i++) {String key = String.valueOf(i);new Thread(()->{myCache.get(key);},String.valueOf(i)).start();}} } 2 準備寫入數據2 2 寫入數據完成2 1 準備寫入數據1 1 寫入數據完成1 3 準備寫入數據3 3 寫入數據完成3 1 準備讀取數據1 2 準備讀取數據2 3 準備讀取數據3 1 讀取數據完成c013a300 2 讀取數據完成347ce814 3 讀取數據完成7387e9c6參考文章
總結
以上是生活随笔為你收集整理的Lock接口Condition,以及Lock与synchronized异同的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021巨量引擎手机行业人群洞察白皮书
- 下一篇: 如何召开一次无效的会议?