之前已經(jīng)說道,JVM提供了synchronized關(guān)鍵字來實現(xiàn)對變量的同步訪問以及用wait和notify來實現(xiàn)線程間通信。在jdk1.5以后,JAVA提供了Lock類來實現(xiàn)和synchronized一樣的功能,并且還提供了Condition來顯示線程間通信。
Lock類是Java類來提供的功能,豐富的api使得Lock類的同步功能比synchronized的同步更強大。本文章的所有代碼均在Lock類例子的代碼
本文主要介紹一下內(nèi)容:
Lock類
Lock類實際上是一個接口,我們在實例化的時候?qū)嶋H上是實例化實現(xiàn)了該接口的類Lock lock = new ReentrantLock();。用synchronized的時候,synchronized可以修飾方法,或者對一段代碼塊進(jìn)行同步處理。
前面講過,針對需要同步處理的代碼設(shè)置對象監(jiān)視器,比整個方法用synchronized修飾要好。Lock類的用法也是這樣,通過Lock對象lock,用lock.lock來加鎖,用lock.unlock來釋放鎖。在兩者中間放置需要同步處理的代碼。
具體的例子如下:
public class MyConditionService { private Lock lock = new ReentrantLock(); public void testMethod(){ lock.lock(); for (int i = 0 ;i < 5;i++){ System.out.println("ThreadName = " + Thread.currentThread().getName() + (" " + (i + 1))); } lock.unlock(); }}測試的代碼如下:
MyConditionService service = new MyConditionService(); new Thread(service::testMethod).start(); new Thread(service::testMethod).start(); new Thread(service::testMethod).start(); new Thread(service::testMethod).start(); new Thread(service::testMethod).start(); Thread.sleep(1000 * 5);
結(jié)果太長就不放出來,具體可以看我源碼。總之,就是每個線程的打印1-5都是同步進(jìn)行,順序沒有亂。
通過下面的例子,可以看出Lock對象加鎖的時候也是一個對象鎖,持續(xù)對象監(jiān)視器的線程才能執(zhí)行同步代碼,其他線程只能等待該線程釋放對象監(jiān)視器。
public class MyConditionMoreService { private Lock lock = new ReentrantLock(); public void methodA(){ try{ lock.lock(); System.out.println("methodA begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); Thread.sleep(1000 * 5); System.out.println("methodA end ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void methodB(){ try{ lock.lock(); System.out.println("methodB begin ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); Thread.sleep(1000 * 5); System.out.println("methodB end ThreadName=" + Thread.currentThread().getName() + " time=" + System.currentTimeMillis()); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } }}測試代碼如下:
public void testMethod() throws Exception { MyConditionMoreService service = new MyConditionMoreService(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadA aa = new ThreadA(service); aa.setName("AA"); aa.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); ThreadB bb = new ThreadB(service); bb.setName("BB"); bb.start(); Thread.sleep(1000 * 30); } public class ThreadA extends Thread{ private MyConditionMoreService service; public ThreadA(MyConditionMoreService service){ this.service = service; } @Override public void run() { service.methodA(); }}public class ThreadB extends Thread{ private MyConditionMoreService service; public ThreadB(MyConditionMoreService service){ this.service = service; } @Override public void run() { super.run(); service.methodB(); }}結(jié)果如下:
methodA begin ThreadName=A time=1485590913520methodA end ThreadName=A time=1485590918522methodA begin ThreadName=AA time=1485590918522methodA end ThreadName=AA time=1485590923525methodB begin ThreadName=B time=1485590923525methodB end ThreadName=B time=1485590928528methodB begin ThreadName=BB time=1485590928529methodB end ThreadName=BB time=1485590933533
可以看出Lock類加鎖確實是對象鎖。針對同一個lock對象執(zhí)行的lock.lock是獲得對象監(jiān)視器的線程才能執(zhí)行同步代碼 其他線程都要等待。
在這個例子中,加鎖,和釋放鎖都是在try-finally。這樣的好處是在任何異常發(fā)生的情況下,都能保障鎖的釋放。
Lock類其他的功能
如果Lock類只有l(wèi)ock和unlock方法也太簡單了,Lock類提供了豐富的加鎖的方法和對加鎖的情況判斷。主要有
實現(xiàn)公平鎖
在實例化鎖對象的時候,構(gòu)造方法有2個,一個是無參構(gòu)造方法,一個是傳入一個boolean變量的構(gòu)造方法。當(dāng)傳入值為true的時候,該鎖為公平鎖。默認(rèn)不傳參數(shù)是非公平鎖。
公平鎖:按照線程加鎖的順序來獲取鎖
非公平鎖:隨機競爭來得到鎖
此外,JAVA還提供isFair()來判斷一個鎖是不是公平鎖。
獲取當(dāng)前線程鎖定的個數(shù)
Java提供了getHoldCount()方法來獲取當(dāng)前線程的鎖定個數(shù)。所謂鎖定個數(shù)就是當(dāng)前線程調(diào)用lock方法的次數(shù)。一般一個方法只會調(diào)用一個lock方法,但是有可能在同步代碼中還有調(diào)用了別的方法,那個方法內(nèi)部有同步代碼。這樣,getHoldCount()返回值就是大于1。
下面的方法用來判斷等待鎖的情況
獲取等待鎖的線程數(shù)
Java提供了getQueueLength()方法來得到等待鎖釋放的線程的個數(shù)。
查詢指定的線程是否等待獲取此鎖定
Java提供了hasQueuedThread(Thread thread)查詢該Thread是否等待該lock對象的釋放。
查詢是否有線程等待獲取此鎖定
同樣,Java提供了一個簡單判斷是否有線程在等待鎖釋放即hasQueuedThreads()。
下面的方法用來判斷持有鎖的情況
查詢當(dāng)前線程是否持有鎖定
Java不僅提供了判斷是否有線程在等待鎖釋放的方法,還提供了是否當(dāng)前線程持有鎖即isHeldByCurrentThread(),判斷當(dāng)前線程是否有此鎖定。
判斷一個鎖是不是被線程持有
同樣,Java提供了簡單判斷一個鎖是不是被一個線程持有,即isLocked()
下面的方法用來實現(xiàn)多種方式加鎖
加鎖時如果中斷則不加鎖,進(jìn)入異常處理
Lock類提供了多種選擇的加鎖方法,lockInterruptibly()也可以實現(xiàn)加鎖,但是當(dāng)線程被中斷的時候,就會加鎖失敗,進(jìn)行異常處理階段。一般這種情況出現(xiàn)在該線程已經(jīng)被打上interrupted的標(biāo)記了。
嘗試加鎖,如果該鎖未被其他線程持有的情況下成功
Java提供了tryLock()方法來進(jìn)行嘗試加鎖,只有該鎖未被其他線程持有的基礎(chǔ)上,才會成功加鎖。
上面介紹了Lock類來實現(xiàn)代碼的同步處理,下面介紹Condition類來實現(xiàn)wait/notify機制。
Condition類
Condition是Java提供了來實現(xiàn)等待/通知的類,Condition類還提供比wait/notify更豐富的功能,Condition對象是由lock對象所創(chuàng)建的。但是同一個鎖可以創(chuàng)建多個Condition的對象,即創(chuàng)建多個對象監(jiān)視器。這樣的好處就是可以指定喚醒線程。notify喚醒的線程是隨機喚醒一個。
下面,看一個例子,顯示簡單的等待/通知
public class ConditionWaitNotifyService { private Lock lock = new ReentrantLock(); public Condition condition = lock.newCondition(); public void await(){ try{ lock.lock(); System.out.println("await的時間為 " + System.currentTimeMillis()); condition.await(); System.out.println("await結(jié)束的時間" + System.currentTimeMillis()); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void signal(){ try{ lock.lock(); System.out.println("sign的時間為" + System.currentTimeMillis()); condition.signal(); }finally { lock.unlock(); } }}測試的代碼如下:
ConditionWaitNotifyService service = new ConditionWaitNotifyService(); new Thread(service::await).start(); Thread.sleep(1000 * 3); service.signal(); Thread.sleep(1000);
結(jié)果如下:
await的時間為 1485610107421sign的時間為1485610110423await結(jié)束的時間1485610110423
condition對象通過lock.newCondition()來創(chuàng)建,用condition.await()來實現(xiàn)讓線程等待,是線程進(jìn)入阻塞。用condition.signal()來實現(xiàn)喚醒線程。喚醒的線程是用同一個conditon對象調(diào)用await()方法而進(jìn)入阻塞。并且和wait/notify一樣,await()和signal()也是在同步代碼區(qū)內(nèi)執(zhí)行。
此外看出await結(jié)束的語句是在獲取通知之后才執(zhí)行,確實實現(xiàn)了wait/notify的功能。下面這個例子是展示喚醒制定的線程。
ConditionAllService service = new ConditionAllService(); Thread a = new Thread(service::awaitA); a.setName("A"); a.start(); Thread b = new Thread(service::awaitB); b.setName("B"); b.start(); Thread.sleep(1000 * 3); service.signAAll(); Thread.sleep(1000 * 4);結(jié)果如下:
begin awaitA時間為 1485611065974ThreadName=Abegin awaitB時間為 1485611065975ThreadName=BsignAll的時間為1485611068979ThreadName=mainend awaitA時間為1485611068979ThreadName=A
該結(jié)果確實展示用同一個condition對象來實現(xiàn)等待通知。
對于等待/通知機制,簡化而言,就是等待一個條件,當(dāng)條件不滿足時,就進(jìn)入等待,等條件滿足時,就通知等待的線程開始執(zhí)行。為了實現(xiàn)這種功能,需要進(jìn)行wait的代碼部分與需要進(jìn)行通知的代碼部分必須放在同一個對象監(jiān)視器里面。執(zhí)行才能實現(xiàn)多個阻塞的線程同步執(zhí)行代碼,等待與通知的線程也是同步進(jìn)行。對于wait/notify而言,對象監(jiān)視器與等待條件結(jié)合在一起 即synchronized(對象)利用該對象去調(diào)用wait以及notify。但是對于Condition類,是對象監(jiān)視器與條件分開,Lock類來實現(xiàn)對象監(jiān)視器,condition對象來負(fù)責(zé)條件,去調(diào)用await以及signal。
Condition類的其他功能
和wait類提供了一個最長等待時間,awaitUntil(Date deadline)在到達(dá)指定時間之后,線程會自動喚醒。但是無論是await或者awaitUntil,當(dāng)線程中斷時,進(jìn)行阻塞的線程會產(chǎn)生中斷異常。Java提供了一個awaitUninterruptibly的方法,使即使線程中斷時,進(jìn)行阻塞的線程也不會產(chǎn)生中斷異常。
讀寫鎖
Lock類除了提供了ReentrantLock的鎖以外,還提供了ReentrantReadWriteLock的鎖。讀寫鎖分成兩個鎖,一個鎖是讀鎖,一個鎖是寫鎖。讀鎖與讀鎖之間是共享的,讀鎖與寫鎖之間是互斥的,寫鎖與寫鎖之間也是互斥的。
看下面的讀讀共享的例子:
public class ReadReadService { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void read(){ try{ try{ lock.readLock().lock(); System.out.println("獲得讀鎖" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(1000 * 10); }finally { lock.readLock().unlock(); } }catch (InterruptedException e){ e.printStackTrace(); } }}測試的代碼和結(jié)果如下:
ReadReadService service = new ReadReadService(); Thread a = new Thread(service::read); a.setName("A"); Thread b = new Thread(service::read); b.setName("B"); a.start(); b.start();結(jié)果如下:
獲得讀鎖A 1485614976979獲得讀鎖B 1485614976981
兩個線程幾乎同時執(zhí)行同步代碼。
下面的例子是寫寫互斥的例子
public class WriteWriteService { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void write(){ try{ try{ lock.writeLock().lock(); System.out.println("獲得寫鎖" + Thread.currentThread().getName() + " " +System.currentTimeMillis()); Thread.sleep(1000 * 10); }finally { lock.writeLock().unlock(); } }catch (InterruptedException e){ e.printStackTrace(); } }}測試代碼和結(jié)果如下:
WriteWriteService service = new WriteWriteService(); Thread a = new Thread(service::write); a.setName("A"); Thread b = new Thread(service::write); b.setName("B"); a.start(); b.start(); Thread.sleep(1000 * 30);結(jié)果如下:
獲得寫鎖A 1485615316519獲得寫鎖B 1485615326524
兩個線程同步執(zhí)行代碼
讀寫互斥的例子:
public class WriteReadService { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void read(){ try{ try{ lock.readLock().lock(); System.out.println("獲得讀鎖" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(1000 * 10); }finally { lock.readLock().unlock(); } }catch (InterruptedException e){ e.printStackTrace(); } } public void write(){ try{ try{ lock.writeLock().lock(); System.out.println("獲得寫鎖" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(1000 * 10); }finally { lock.writeLock().unlock(); } }catch (InterruptedException e){ e.printStackTrace(); } }}測試的代碼如下:
WriteReadService service = new WriteReadService(); Thread a = new Thread(service::write); a.setName("A"); a.start(); Thread.sleep(1000); Thread b = new Thread(service::read); b.setName("B"); b.start(); Thread.sleep(1000 * 30);結(jié)果如下:
獲得寫鎖A 1485615633790獲得讀鎖B 1485615643792
兩個線程讀寫之間也是同步執(zhí)行代碼。
總結(jié)
本文介紹了新的同步代碼的方式Lock類以及新的等待/通知機制的實現(xiàn)Condition類。本文只是很簡單的介紹了他們的概念和使用的方式。關(guān)于Condition以及讀寫鎖還有更多的內(nèi)容,將放在以后的博客中。
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時也希望多多支持武林網(wǎng)!
新聞熱點
疑難解答
圖片精選