從jdk發行1.5版本以後,在原來synchronize的基礎上,增長了重入鎖ReentrantLock。java
本文就不介紹synchronize了,有興趣的同窗能夠去了解一下,本文重點介紹ReentrantLock。git
併發編程的時候,好比說有一個業務是讀寫操做,那多個線程執行這個業務就會形成已經寫入的數據又寫一遍,就會形成數據錯亂。github
因此須要引入鎖,進行數據同步,強制使得該業務執行的時候只有一個線程在執行,從而保證不會插入多條重複數據。編程
一些共享資源也是須要加鎖,從而保證數據的一致性。多線程
關於鎖的概念,也就不過多篇幅介紹,有不少概念性的東西,須要本身取找本書狠狠啃一啃,本文主要是給你們介紹如何使用鎖。併發
首先來看第一個實例:用兩個線程來在控制檯有序打出1,2,3。ide
public class FirstReentrantLock { public static void main(String[] args) { Runnable runnable = new ReentrantLockThread(); new Thread(runnable, "a").start(); new Thread(runnable, "b").start(); } } class ReentrantLockThread implements Runnable { @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "輸出了: " + i); } } }
執行FirstReentrantLock ,查看控制檯輸出:高併發
能夠看到,並無順序,雜亂無章。this
那使用ReentrantLock加入鎖,代碼以下:spa
package com.chapter2; import java.util.concurrent.locks.ReentrantLock; /** * @author tangj * * 如何使用ReentrantLock */ public class FirstReentrantLock { public static void main(String[] args) { Runnable runnable = new ReentrantLockThread(); new Thread(runnable, "a").start(); new Thread(runnable, "b").start(); } } class ReentrantLockThread implements Runnable { // 建立一個ReentrantLock對象 ReentrantLock lock = new ReentrantLock(); @Override public void run() { try { // 使用lock()方法加鎖 lock.lock(); for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "輸出了: " + i); } } finally { // 別忘了執行unlock()方法釋放鎖 lock.unlock(); } } }
執行FirstReentrantLock ,查看控制檯輸出:
有順序的打印出了0,1,2,0,1,2.
這就是鎖的做用,它是互斥的,當一個線程持有鎖的時候,其餘線程只能等待,待該線程執行結束,再經過競爭獲得鎖。
一般在開發併發程序的時候,會碰到須要中止正在執行業務A,來執行另外一個業務B,當業務B執行完成後業務A繼續執行。ReentrantLock經過Condtion等待/喚醒這樣的機制.
相比較synchronize的wait()和notify()/notifAll()的機制而言,Condition具備更高的靈活性,這個很關鍵。Conditon能夠實現多路通知和選擇性通知。
當使用notify()/notifAll()時,JVM時隨機通知線程的,具備很大的不可控性,因此建議使用Condition。
Condition使用起來也很是方便,只須要註冊到ReentrantLock下面便可。
參考下圖:
接下來,使用Condition來實現等待/喚醒,而且可以喚醒制定線程
先寫業務代碼:
package com.chapter2.howtocondition; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class MyService { // 實例化一個ReentrantLock對象 private ReentrantLock lock = new ReentrantLock(); // 爲線程A註冊一個Condition public Condition conditionA = lock.newCondition(); // 爲線程B註冊一個Condition public Condition conditionB = lock.newCondition(); public void awaitA() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "進入了awaitA方法"); long timeBefore = System.currentTimeMillis(); // 執行conditionA等待 conditionA.await(); long timeAfter = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName()+"被喚醒"); System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore)/1000+"s"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void awaitB() { try { lock.lock(); System.out.println(Thread.currentThread().getName() + "進入了awaitB方法"); long timeBefore = System.currentTimeMillis(); // 執行conditionB等待 conditionB.await(); long timeAfter = System.currentTimeMillis(); System.out.println(Thread.currentThread().getName()+"被喚醒"); System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore)/1000+"s"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signallA() { try { lock.lock(); System.out.println("啓動喚醒程序"); // 喚醒全部註冊conditionA的線程 conditionA.signalAll(); } finally { lock.unlock(); } } public void signallB() { try { lock.lock(); System.out.println("啓動喚醒程序"); // 喚醒全部註冊conditionA的線程 conditionB.signalAll(); } finally { lock.unlock(); } } }
分別實例化了兩個Condition對象,都是使用同一個lock註冊。注意conditionA對象的等待和喚醒只對使用了conditionA的線程有用,同理conditionB對象的等待和喚醒只對使用了conditionB的線程有用。
繼續寫兩個線程的代碼:
package com.chapter2.howtocondition; public class MyServiceThread1 implements Runnable { private MyService service; public MyServiceThread1(MyService service) { this.service = service; } @Override public void run() { service.awaitA(); } }
注意:MyServiceThread1 使用了awaitA()方法,持有的是conditionA!
package com.chapter2.howtocondition; public class MyServiceThread2 implements Runnable { private MyService service; public MyServiceThread2(MyService service) { this.service = service; } @Override public void run() { service.awaitB(); } }
注意:MyServiceThread2 使用了awaitB()方法,持有的是conditionB!
最後看啓動類:
package com.chapter2.howtocondition; public class ApplicationCondition { public static void main(String[] args) throws InterruptedException { MyService service = new MyService(); Runnable runnable1 = new MyServiceThread1(service); Runnable runnable2 = new MyServiceThread2(service); new Thread(runnable1, "a").start(); new Thread(runnable2, "b").start(); // 線程sleep2秒鐘 Thread.sleep(2000); // 喚醒全部持有conditionA的線程 service.signallA(); Thread.sleep(2000); // 喚醒全部持有conditionB的線程 service.signallB(); } }
執行ApplicationCondition ,來看控制檯輸出結果:
a和b都進入各自的await()方法。首先執行的是
使用conditionA的線程被喚醒,然後再喚醒使用conditionB的線程。
學會使用Condition,那來用它實現生產者消費者模式
首先來看業務類的實現:
package com.chapter2.consumeone; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Service { private Lock lock = new ReentrantLock(); private boolean flag = false; private Condition condition = lock.newCondition(); // 以此爲衡量標誌 private int number = 1; /** * 生產者生產 */ public void produce() { try { lock.lock(); while (flag == true) { condition.await(); } System.out.println(Thread.currentThread().getName() + "-----生產-----"); number++; System.out.println("number: " + number); System.out.println(); flag = true; // 提醒消費者消費 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } /** * 消費者消費生產的物品 */ public void consume() { try { lock.lock(); while (flag == false) { condition.await(); } System.out.println(Thread.currentThread().getName() + "-----消費-----"); number--; System.out.println("number: " + number); System.out.println(); flag = false; // 提醒生產者生產 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
生產者線程代碼:
package com.chapter2.consumeone; /** * 生產者線程 * * @author tangj * */ public class MyThreadProduce implements Runnable { private Service service; public MyThreadProduce(Service service) { this.service = service; } @Override public void run() { for (;;) { service.produce(); } } }
消費者線程代碼:
package com.chapter2.consumeone; /** * 消費者線程 * * @author tangj * */ public class MyThreadConsume implements Runnable { private Service service; public MyThreadConsume(Service service) { super(); this.service = service; } @Override public void run() { for (;;) { service.consume(); } } }
啓動類:
package com.chapter2.consumeone; public class Application { public static void main(String[] args) { Service service = new Service(); Runnable produce = new MyThreadProduce(service); Runnable consume = new MyThreadConsume(service); new Thread(produce, "生產者 ").start(); new Thread(consume, "消費者 ").start(); } }
執行Application,看控制檯的輸出:
由於採用了無限循環,生產者線程和消費者線程會一直處於工做狀態,能夠看到,生產者線程執行完畢後,消費者線程就會執行,以這樣的交替順序,
並且的number也遵循者生產者生產+1,消費者消費-1的一個狀態。這個就是使用ReentrantLock和Condition來實現的生產者消費者模式。
充分發掘Condition的靈活性,能夠用它來實現順序執行線程。
來看業務類代碼:
package com.chapter2.sequencethread; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class Service { // 經過nextThread控制下一個執行的線程 private static int nextThread = 1; private ReentrantLock lock = new ReentrantLock(); // 有三個線程,全部註冊三個Condition Condition conditionA = lock.newCondition(); Condition conditionB = lock.newCondition(); Condition conditionC = lock.newCondition(); public void excuteA() { try { lock.lock(); while (nextThread != 1) { conditionA.await(); } System.out.println(Thread.currentThread().getName() + " 工做"); nextThread = 2; conditionB.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void excuteB() { try { lock.lock(); while (nextThread != 2) { conditionB.await(); } System.out.println(Thread.currentThread().getName() + " 工做"); nextThread = 3; conditionC.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void excuteC() { try { lock.lock(); while (nextThread != 3) { conditionC.await(); } System.out.println(Thread.currentThread().getName() + " 工做"); nextThread = 1; conditionA.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
這裏能夠看到,註冊了三個Condition,分別用於三個線程的等待和通知。
啓動類代碼:
package com.chapter2.sequencethread; /** * 線程按順序執行 * * @author tangj * */ public class Application { private static Runnable getThreadA(final Service service) { return new Runnable() { @Override public void run() { for (;;) { service.excuteA(); } } }; } private static Runnable getThreadB(final Service service) { return new Runnable() { @Override public void run() { for (;;) { service.excuteB(); } } }; } private static Runnable getThreadC(final Service service) { return new Runnable() { @Override public void run() { for (;;) { service.excuteC(); } } }; } public static void main(String[] args) { Service service = new Service(); Runnable A = getThreadA(service); Runnable B = getThreadB(service); Runnable C = getThreadC(service); new Thread(B, "B").start(); new Thread(A, "A").start(); new Thread(C, "C").start(); } }
運行啓動類,查看控制檯輸出結果:
A,B,C三個線程一直按照順序執行。
學會使用鎖是學好多線程的基礎,ReentrantLock相比較關鍵字synchronize而言,更加並且可控,因此仍是推薦你們使用ReentrantLock。
本文因此代碼都更新到個人github中,你們能夠去clone或者Fork,我會持續更新的。
點擊這裏進入個人Github喜歡的朋友能夠點擊下方的推薦,或者寫個評論咱們共同探討Java高併發!!!