(四)Lock,ReentrantLock,ReentrantReadWriteLock類的使用以及相關api---synchronized進階
這篇部落格記錄了Lock,ReentrantLock,ReentrantReadWriteLock類的使用以及其一些api:
碼字不易~~另外《java多執行緒程式設計核心技術》這本書讀著很爽
前言說明:之前為了解決多執行緒時的非執行緒安全問題,使用的是synchronized。接下來記錄的是他的升級版本ReentrantLock,更加靈活,可控性更高,而ReentrantReadWriteLock類是對ReentrantLock類的補充,能夠在某些條件之間之下提交效率
下面先來看下都有哪些api,以及和synchronized之間是怎樣對應的吧。
以前使用鎖完成同步是將同步程式碼塊寫在synchronized之內,現在我們使用
Lock lock = new ReentrantLock();
來宣告一個鎖,他有這兩個方法
lock.lock(); 和 lock.unlock(); 這兩個是配套的,在其之間的程式碼就是同步程式碼塊。
和之前一樣,lock()方法會讓當前執行緒持有物件監聽器,具體規則之類的和synchronized也一樣,
比如下面的例子,MyService有一段程式碼上鎖,自定義執行緒類呼叫它
MyService.java
package 第四章; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MyService { private Lock lock = new ReentrantLock(); public void testMethod(){ lock.lock(); for(int i=0;i<5;i++){ System.out.println(i+"執行緒:"+Thread.currentThread().getName()); } lock.unlock(); } }
MyThread.java
package 第四章; public class MyThread extends Thread { private MyService myService; public MyThread(MyService myService) { super(); this.myService = myService; } public void run(){ myService.testMethod(); } }View Code
test.java
package 第四章; public class test { public static void main(String[] args){ MyThread[] threads = new MyThread[5]; MyService myService = new MyService(); for(int i=0;i<5;i++){ threads[i] = new MyThread(myService); threads[i].start(); } } }View Code
執行結果:
可以看到執行緒之間是同步執行的,當然前提是同一個MyService物件。
之前的wait/notify,用Condition物件來替換:
效率提高的地方以及原因:
Condition物件可以對同一個鎖宣告多個,相當於每當讓執行緒等待時,他都有自己的喚醒condition,換句話說,每一個執行緒都可以註冊一個Condition,這樣當我們喚醒執行緒的時候,就可以喚醒指定的執行緒,比如之前的生產者消費者模型之中的假死現象,我們使用過notifyAll()來解決的,但是這種方法喚醒了所有的執行緒,讓所有執行緒都去爭搶cpu,但是我們事實上指向喚醒異類執行緒,並不想喚醒同類,全部喚醒的話,效率是一個問題。那麼現在,給每一個執行緒都註冊
一個Condition,這樣子喚醒時候,我們就可以喚醒指定的執行緒,提高了效率,也更加靈活。
下面的是一個簡單的await/signal例子,展示了基本的使用:await類似之前的wait,signal類似於notify:signalAll()喚醒全部
更改之前的MyService.java
condition.await()讓執行緒阻塞,condition.signal()隨機喚醒一個由當前condition註冊的執行緒
package 第四章; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; public class MyService { private Lock lock = new ReentrantLock(); public Condition condition = lock.newCondition(); public void testMethod(){ try{ lock.lock(); System.out.println("即將開始迴圈"); condition.await(); for(int i=0;i<2;i++){ System.out.println(i+"執行緒:"+Thread.currentThread().getName()); } }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void signal(){ try{ lock.lock(); this.condition.signal(); System.out.println("喚醒了一個執行緒"); }finally { lock.unlock(); } } }View Code
MyThread.java不變
test.java:先讓執行緒全部阻塞,然後呼叫自定義的signal方法喚醒執行緒,
package 第四章; public class test { public static void main(String[] args){ MyThread[] threads = new MyThread[5]; MyService myService = new MyService(); for(int i=0;i<5;i++){ threads[i] = new MyThread(myService); threads[i].start(); } try{ Thread.sleep(1000); myService.signal(); }catch (InterruptedException e){ e.printStackTrace(); } } }View Code
執行結果如下:
可以看到,我們成功喚醒了一個執行緒。
下面的例子喚醒了一個指定的執行緒
MyService.java:根據當前執行緒的名字讓指定的Condition物件等待,並書寫兩個喚醒不同的Condition物件註冊的執行緒
package 第四章; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.Condition; public class MyService { private Lock lock = new ReentrantLock(); public Condition conditionA = lock.newCondition(); public Condition conditionB = lock.newCondition(); public void testMethod(){ try{ lock.lock(); System.out.println("執行緒"+Thread.currentThread().getName()+"等待中..."); if(Thread.currentThread().getName().equals("A")) conditionA.await(); else conditionB.await(); for(int i=0;i<2;i++){ System.out.println(i+"執行緒:"+Thread.currentThread().getName()); } }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void signalA(){ try{ lock.lock(); this.conditionA.signal(); System.out.println("喚醒了A執行緒"); }finally { lock.unlock(); } } public void signalB(){ try{ lock.lock(); this.conditionB.signal(); System.out.println("喚醒了B執行緒"); }finally { lock.unlock(); } } }View Code
test.java,啟動A,B兩個執行緒,只喚醒A執行緒
package 第四章; public class test { public static void main(String[] args){ MyService myService = new MyService(); MyThread myThreadA = new MyThread(myService); myThreadA.setName("A"); MyThread myThreadB = new MyThread(myService); myThreadB.setName("B"); myThreadA.start(); myThreadB.start(); try{ Thread.sleep(1000); myService.signalA(); }catch (InterruptedException e){ e.printStackTrace(); } } }View Code
執行結果:
根據程式碼,我們可以看到可以通過不同Condition物件來喚醒指定的執行緒。
用處:
1.可以想到,如果用Lock來解決之前的多消費多生產者時的假死問題,我們可以將生產者統一註冊一個Condition,消費者統一註冊一個Condition,每一次喚醒對方的Condition,這樣子就不會出現連續喚醒同類導致假死的情況了,並且可以避免喚醒所有執行緒,導致效率低下。
2.我們也可以按照我們想要的順序進行喚醒,只要你註冊了正確的Condition物件
公平鎖和非公平鎖:
比較好理解,公平鎖相當於一個佇列,先進先出,先執行的執行緒先拿到鎖,後執行的後拿到鎖,按照順序來,非公平鎖就是鎖的搶佔是隨機的,沒有順序。
預設是非公平鎖,建立Lock時加上true引數即為公平鎖:
Lock lock =new ReentrantLock(true);
下面介紹一些ReentrantLock的api,
一般在一些定製化的情況可能會用到,emmm,這塊先了解一下,知道有這些就行,emmm,說實話目前我感覺這個沒啥用,有個印象,不過注意使用這些API使,必須以下面這種方式new物件
ReentrantLock lock = new ReentrantLock();
(lock.)GetHoldCount():查詢當前執行緒有保持著幾個lock鎖,簡單來講就是當前執行緒呼叫了幾次lock()方法
GetQueueLength():有多少個執行緒在等待獲取當前鎖,可以理解為有多少個沒有拿到當前鎖,
getWaitQueueLength(Condition condition):有多少個執行緒處於阻塞狀態,並且是執行了引數Condition物件所對應的await()方法導致阻塞的。
hasQueuedThread(Thread thread):查詢指定的執行緒是否正在等待獲取當前鎖
hasQueuedThreads():查詢是否有執行緒正在等待獲取當前鎖
hasWaiters(Condition):查詢是否有執行緒是由於呼叫了引數Condition.await()導致阻塞的。
isHeldByCurrentThread():查詢當前執行緒是否持有當前鎖
isLocked():當前鎖是否被某個執行緒持有
awaitUninterruptibly():這也是一種讓當前執行緒阻塞的方法,不過await呼叫之後如果再使用Interrupt等程式碼阻塞當前程序會報異常,但是這個不會,相當於讓當前執行緒變成可以阻塞的執行緒,,,,不懂有撒用
awaitUntil(Date):阻塞當前執行緒,如果在指定時間之前還沒有被喚醒,則喚醒他。引數也可以傳Calendar.getTime(),Calendar類用於處理時間
ReentrantReadWriteLock類
之前的ReentrantLock相當於同一時間只有一個執行緒在執行程式碼。但是在不涉及更改例項變數的程式碼之中,我們可以允許非同步執行來加快效率, 而一些涉及到更改例項變數的程式碼,這時候同步執行(這時候非同步可能出現非執行緒安全),這樣可以在一定程度上加快效率,這就是這個類的作用。
簡單來說,我們一般有讀寫兩個操作,如果多個執行緒執行讀操作,ok,非同步執行,如果多個執行緒有的執行讀,有的寫,ok,同步執行,這個類就是自動完成這個事情,你只需要在鎖時使用不同型別的鎖就行。
下面是一個例子,讀讀非同步(其他全部同步):
ReadAndWrite.java 代表具體的操作,讀,寫,輸出當前操作以及時間,sleep()模擬操作耗費的時間
package 第四章; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadAndWrite { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public void read(){ try{ lock.readLock().lock(); System.out.println("讀操作"+System.currentTimeMillis()); Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.readLock().unlock(); } } public void write(){ try{ lock.writeLock().lock(); System.out.println("寫操作"+System.currentTimeMillis()); Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.writeLock().unlock(); } } }View Code
MyThread2.java:裡面有兩個java類,一個執行讀操作,一個寫操作
package 第四章; class MyThreadRead extends Thread{ private ReadAndWrite readAndWrite; public MyThreadRead(ReadAndWrite readAndWrite) { this.readAndWrite = readAndWrite; } public void run(){ this.readAndWrite.read(); } } class MyThreadWrite extends Thread{ private ReadAndWrite readAndWrite; public MyThreadWrite(ReadAndWrite readAndWrite) { this.readAndWrite = readAndWrite; } public void run(){ this.readAndWrite.write(); } }View Code
test.java: 建立三個讀執行緒
package 第四章; public class test { public static void main(String[] args){ ReadAndWrite readAndWrite = new ReadAndWrite(); MyThreadRead reads[] = new MyThreadRead[3]; for(int i=0;i<3;i++) { reads[i] = new MyThreadRead(readAndWrite); reads[i].start(); } } }View Code
執行結果:
可以看到,三個讀操作時同時執行的。
下面更改test.java,建立三個讀執行緒,三個寫執行緒:
test.java
package 第四章; public class test { public static void main(String[] args){ ReadAndWrite readAndWrite = new ReadAndWrite(); MyThreadWrite writes[] = new MyThreadWrite[3]; for(int i=0;i<3;i++) { writes[i] = new MyThreadWrite(readAndWrite); writes[i].start(); } MyThreadRead reads[] = new MyThreadRead[3]; for(int i=0;i<3;i++) { reads[i] = new MyThreadRead(readAndWrite); reads[i].start(); } } }View Code
執行:
可以看到,寫操作之間是互斥的,相當於同步,一個一個執行的,讀的時候就是非同步的,
,,好嘞,就演示這幾個,其他的都同理,只有讀讀是非同步的,讀寫同步,你可以交替著start看一下,如下:
好滴,第四章就這些暫時。。
&n