鎖的概念
從jdk發行1.5版本之后,在原來synchronize的基礎上,增加了重入鎖ReentrantLock。
本文就不介紹synchronize了,有興趣的同學可以去了解一下,本文重點介紹ReentrantLock。
鎖是什么?
並發編程的時候,比如說有一個業務是讀寫操作,那多個線程執行這個業務就會造成已經寫入的數據又寫一遍,就會造成數據錯亂。
所以需要引入鎖,進行數據同步,強制使得該業務執行的時候只有一個線程在執行,從而保證不會插入多條重復數據。
一些共享資源也是需要加鎖,從而保證數據的一致性。
關於鎖的概念,也就不過多篇幅介紹,有很多概念性的東西,需要自己取找本書狠狠啃一啃,本文主要是給大家介紹如何使用鎖。
使用ReentrantLock同步
首先來看第一個實例:用兩個線程來在控制台有序打出1,2,3。
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 ,查看控制台輸出:
可以看到,並沒有順序,雜亂無章。
那使用ReentrantLock加入鎖,代碼如下:
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.
這就是鎖的作用,它是互斥的,當一個線程持有鎖的時候,其他線程只能等待,待該線程執行結束,再通過競爭得到鎖。
使用Condition實現線程等待和喚醒
通常在開發並發程序的時候,會碰到需要停止正在執行業務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高並發!!!