Lock接口Condition,以及Lock与synchronized异同
一、synchronized與Lock
基于synchronized關(guān)鍵字去實(shí)現(xiàn)同步的,(jvm內(nèi)置鎖,也叫隱式鎖,由我們的jvm自動(dòng)去加鎖跟解鎖的)juc下的基于Lock接口的這樣的一種鎖的實(shí)現(xiàn)方式(顯式鎖,他需要我們手動(dòng)的去加鎖,跟解鎖)
一張圖了解一下區(qū)別:
二、Lock接口的實(shí)現(xiàn)
Lock鎖實(shí)現(xiàn)提供了比使用同步方法和語句可以獲得的更廣泛的鎖操作。它們允許更靈活的結(jié)構(gòu),可能具有非常不同的屬性,并且可能支持多個(gè)關(guān)聯(lián)的條件對象。
三、Lock實(shí)現(xiàn)ReentrantLock
由英文知道:ReentrantLock稱是重入鎖,可以反復(fù)得到相同的一把鎖,它有一個(gè)與鎖相關(guān)的獲取計(jì)數(shù)器,如果擁有鎖的某個(gè)線程再次得到鎖,那么獲取計(jì)數(shù)器就加1,然后鎖需要被釋放兩次才能獲得真正釋放(重入鎖)。圍繞著去實(shí)現(xiàn)的核心實(shí)質(zhì)就是AQS(AbstractQueuedSynchronizer)(抽象隊(duì)列同步器)ReentrantLock和synchronized關(guān)鍵字一樣可以用來實(shí)現(xiàn)線程之間的同步互斥,但是在功能是比synchronized關(guān)鍵字更強(qiáng)大而且更靈活。
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會(huì)輸出一個(gè)小于200的數(shù)注意:ReentrantLock并不是一種替代內(nèi)置加鎖的方法,而是作為一種可選擇的高級(jí)功能。相比于synchronized,ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時(shí)、公平鎖等特點(diǎn)。
以下是他和synchronized的對比:
- 1.synchronized是獨(dú)占鎖,加鎖和解鎖的過程自動(dòng)進(jìn)行,易于操作,但不夠靈活。ReentrantLock也是獨(dú)占鎖,加鎖和解鎖的過程需要手動(dòng)進(jìn)行,不易操作,但非常靈活。
- 2.synchronized可重入,因?yàn)榧渔i和解鎖自動(dòng)進(jìn)行,不必?fù)?dān)心最后是否釋放鎖;ReentrantLock也可重入,但加鎖和解鎖需要手動(dòng)進(jìn)行(其實(shí)這也是Lock的相比synchronized的優(yōu)點(diǎn),更加靈活),且次數(shù)需一樣,否則其他線程無法獲得鎖。
- 3.synchronized不可響應(yīng)中斷,一個(gè)線程獲取不到鎖就一直等著;ReentrantLock可以相應(yīng)中斷。
- 4.ReentrantLock可設(shè)置超時(shí)退出,不會(huì)永久等待構(gòu)成死鎖。
在synchronized和reentrantLock之間進(jìn)行選擇:
雖然reentrantLock在1.5和1.6的表現(xiàn)中,性能遠(yuǎn)好于synchronized,但還是提倡使用synchronized:
- 1.reentrantLock忘記關(guān)鎖的話,很危險(xiǎn)
- 2.未來更可能提升的是 synchronized的性能,而不是reentrantLock
- 3.當(dāng)需要用到某些高級(jí)性能時(shí),才考慮reentrantLock,例如:可定時(shí)的,可輪詢的,可中斷的,公平隊(duì)列等
公平鎖與非公平鎖(AQS的原因)
AQS內(nèi)部維護(hù)著一個(gè)FIFO的隊(duì)列,即CLH隊(duì)列。AQS的同步機(jī)制就是依靠CLH隊(duì)列實(shí)現(xiàn)的。
- 一般意義上的鎖是不公平的,不一定先來的線程能先得到鎖,后來的線程就后得到鎖。不公平的鎖可能會(huì)產(chǎn)生饑餓現(xiàn)象。
- 公平鎖的意思就是,這個(gè)鎖能保證線程是先來的先得到鎖。雖然公平鎖不會(huì)產(chǎn)生饑餓現(xiàn)象,但是公平鎖的性能會(huì)比非公平鎖差很多。
公平鎖:
非公平鎖:
注意: 默認(rèn)情況下ReentranLock類使用的是非公平鎖,若要使用公平鎖,如下 :
四、Condition接口
我們知道synchronized關(guān)鍵字與wait()和notify/notifyAll()方法相配合就可以實(shí)現(xiàn)等待/通知機(jī)制,借助于Condition接口與newCondition() 方法ReentrantLock類也可以實(shí)現(xiàn)。Condition是JDK1.5之后才有的,它具有很好的靈活性,比如可以實(shí)現(xiàn)多路通知功能也就是在一個(gè)Lock對象中可以創(chuàng)建多個(gè)Condition實(shí)例(即對象監(jiān)視器),線程對象可以注冊在指定的Condition中,從而可以有選擇性的進(jìn)行線程通知,在調(diào)度線程上更加靈活。 condition可以通俗的理解為條件隊(duì)列。當(dāng)一個(gè)線程在調(diào)用了await方法以后,直到線程等待的某個(gè)條件為真的時(shí)候才會(huì)被喚醒。這種方式為線程提供了更加簡單的等待/通知模式。Condition必須要配合鎖一起使用,因?yàn)閷蚕頎顟B(tài)變量的訪問發(fā)生在多線程環(huán)境下。一個(gè)Condition的實(shí)例必須與一個(gè)Lock綁定,因此Condition一般都是作為Lock的內(nèi)部實(shí)現(xiàn)。
在使用notify/notifyAll()方法進(jìn)行通知時(shí),被通知的線程是由JVM選擇的,使用ReentrantLock類結(jié)合Condition實(shí)例可以實(shí)現(xiàn)“選擇性通知”,這個(gè)功能非常重要,而且是Condition接口默認(rèn)提供的。而synchronized關(guān)鍵字就相當(dāng)于整個(gè)Lock對象中只有一個(gè)Condition實(shí)例,所有的線程都注冊在它一個(gè)身上。如果執(zhí)行notifyAll()方法的話就會(huì)通知所有處于等待狀態(tài)的線程這樣會(huì)造成很大的效率問題,而Condition實(shí)例的signalAll()方法 只會(huì)喚醒注冊在該Condition實(shí)例中的所有等待線程。
簡單地說:一個(gè)線程運(yùn)行完之后通過condition.signal()或者condition.signalAll()方法通知下一個(gè)特定的運(yùn)行,就這樣循環(huán)往復(fù)即可。
五、ReadWriteLock接口的實(shí)現(xiàn)類:ReentrantReadWriteLock
ReentrantLock(排他鎖)具有完全互斥排他的效果,即同一時(shí)刻只允許一個(gè)線程訪問,這樣做雖然雖然保證了實(shí)例變量的線程安全性,但效率非常低下。ReadWriteLock接口的實(shí)現(xiàn)類ReentrantReadWriteLock讀寫鎖就是為了解決這個(gè)問題。讀寫鎖維護(hù)了兩個(gè)鎖,一個(gè)是讀操作相關(guān)的鎖也成為共享鎖,一個(gè)是寫操作相關(guān)的鎖也稱為排他鎖。通過分離讀鎖和寫鎖,其并發(fā)性比一般排他鎖有了很大提升。多個(gè)讀鎖之間不互斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥(只要出現(xiàn)寫操作的過程就是互斥的。)。在沒有線程Thread進(jìn)行寫入操作時(shí),進(jìn)行讀取操作的多個(gè)Thread都可以獲取讀鎖,而進(jìn)行寫入操作的Thread只有在獲取寫鎖后才能進(jìn)行寫入操作。即多個(gè)Thread可以同時(shí)進(jìn)行讀取操作,但是同一時(shí)刻只允許一個(gè)Thread進(jìn)行寫入操作。
ReentrantReadWriteLock的特性:
ReentrantReadWriteLock常見方法: 構(gòu)造方法
問題例子:
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);//暫停一會(huì)兒線程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();}} } //結(jié)果(還沒寫完就重新再寫一個(gè)) 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 準(zhǔn)備寫入數(shù)據(jù)"+key);TimeUnit.MILLISECONDS.sleep(200);map.put(key, value);System.out.println(Thread.currentThread().getName()+"\t 寫入數(shù)據(jù)完成"+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 準(zhǔn)備讀取數(shù)據(jù)"+key);TimeUnit.MILLISECONDS.sleep(200);String value = map.get(key);System.out.println(Thread.currentThread().getName()+"\t 讀取數(shù)據(jù)完成"+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 準(zhǔn)備寫入數(shù)據(jù)2 2 寫入數(shù)據(jù)完成2 1 準(zhǔn)備寫入數(shù)據(jù)1 1 寫入數(shù)據(jù)完成1 3 準(zhǔn)備寫入數(shù)據(jù)3 3 寫入數(shù)據(jù)完成3 1 準(zhǔn)備讀取數(shù)據(jù)1 2 準(zhǔn)備讀取數(shù)據(jù)2 3 準(zhǔn)備讀取數(shù)據(jù)3 1 讀取數(shù)據(jù)完成c013a300 2 讀取數(shù)據(jù)完成347ce814 3 讀取數(shù)據(jù)完成7387e9c6參考文章
總結(jié)
以上是生活随笔為你收集整理的Lock接口Condition,以及Lock与synchronized异同的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2021巨量引擎手机行业人群洞察白皮书
- 下一篇: 如何召开一次无效的会议?