本文源碼:GitHub·點這裏 || GitEE·點這裏java
Lock加鎖相關結構中涉及兩個使用普遍的基礎API:ReentrantLock類和Condition接口,基本關係以下:git
Lock接口github
Java併發編程中資源加鎖的根接口之一,規定了資源鎖使用的幾個基礎方法。面試
ReentrantLock類編程
實現Lock接口的可重入鎖,即線程若是得到當前實例的鎖,並進入任務方法,在線程沒有釋放鎖的狀態下,能夠再次進入任務方法,特色:互斥排它性,即同一個時刻只有一個線程進入任務。segmentfault
Condition接口多線程
Condition接口描述可能會與鎖有關聯的條件變量,提供了更強大的功能,例如在線程的等待/通知機制上,Conditon能夠實現多路通知和選擇性通知。併發
生產消費模式ide
寫線程向容器中添加數據,讀線程從容器獲取數據,若是容器爲空時,讀線程等待。高併發
public class LockAPI01 { private static Lock lock = new ReentrantLock() ; private static Condition condition1 = lock.newCondition() ; private static Condition condition2 = lock.newCondition() ; public static void main(String[] args) throws Exception { List<String> dataList = new ArrayList<>() ; ReadList readList = new ReadList(dataList); WriteList writeList = new WriteList(dataList); new Thread(readList).start(); TimeUnit.SECONDS.sleep(2); new Thread(writeList).start(); } // 讀數據線程 static class ReadList implements Runnable { private List<String> dataList ; public ReadList (List<String> dataList){ this.dataList = dataList ; } @Override public void run() { lock.lock(); try { if (dataList.size() != 2){ System.out.println("Read wait..."); condition1.await(); } System.out.println("ReadList WakeUp..."); for (String element:dataList){ System.out.println("ReadList:"+element); } condition2.signalAll(); } catch (InterruptedException e){ e.fillInStackTrace() ; } finally { lock.unlock(); } } } // 寫數據線程 static class WriteList implements Runnable { private List<String> dataList ; public WriteList (List<String> dataList){ this.dataList = dataList ; } @Override public void run() { lock.lock(); try { dataList.add("Java") ; dataList.add("C++") ; condition1.signalAll(); System.out.println("Write over..."); condition2.await(); System.out.println("Write WakeUp..."); } catch (InterruptedException e){ e.fillInStackTrace() ; } finally { lock.unlock(); } } } }
這個生產消費模式和生活中的點餐場景極爲相似,用戶下單,通知後廚烹飪,烹飪完成以後通知送餐。
順序執行模式
既然線程執行能夠互相通知,那也能夠基於該機制實現線程的順序執行,基本思路:在一個線程執行完畢後,基於條件喚醒下個線程。
public class LockAPI02 { public static void main(String[] args) { PrintInfo printInfo = new PrintInfo() ; ExecutorService service = Executors.newFixedThreadPool(3); service.execute(new PrintA(printInfo)); service.execute(new PrintB(printInfo)); service.execute(new PrintC(printInfo)); } } class PrintA implements Runnable { private PrintInfo printInfo ; public PrintA (PrintInfo printInfo){ this.printInfo = printInfo ; } @Override public void run() { printInfo.printA (); } } class PrintB implements Runnable { private PrintInfo printInfo ; public PrintB (PrintInfo printInfo){ this.printInfo = printInfo ; } @Override public void run() { printInfo.printB (); } } class PrintC implements Runnable { private PrintInfo printInfo ; public PrintC (PrintInfo printInfo){ this.printInfo = printInfo ; } @Override public void run() { printInfo.printC (); } } class PrintInfo { // 控制下個執行的線程 private String info = "A"; private ReentrantLock lock = new ReentrantLock(); // 三個線程,三個控制條件 Condition conditionA = lock.newCondition(); Condition conditionB = lock.newCondition(); Condition conditionC = lock.newCondition(); public void printA (){ try { lock.lock(); while (!info.equals("A")) { conditionA.await(); } System.out.print("A"); info = "B"; conditionB.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB (){ try { lock.lock(); while (!info.equals("B")) { conditionB.await(); } System.out.print("B"); info = "C"; conditionC.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC (){ try { lock.lock(); while (!info.equals("C")) { conditionC.await(); } System.out.print("C"); info = "A"; conditionA.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
該案例常常出如今多線程的面試題中,如何實現ABC的順序打印問題,基本思路就是基於線程的等待通知機制,可是實現方式不少,上述只是其中一種方式。
重入鎖的排它特性決定了性能會產生瓶頸,爲了提高性能問題,JDK中還有另外一套讀寫鎖機制。讀寫鎖中維護一個共享讀鎖和一個排它寫鎖,在實際開發中,讀的場景仍是偏多的,因此讀寫鎖能夠很好的提升併發性。
讀寫鎖相關結構中兩個基礎API:ReadWriteLock接口和ReentrantReadWriteLock實現類,基本關係以下:
ReadWriteLock
提供兩個基礎方法,readLock獲取讀機制鎖,writeLock獲取寫機制鎖。
ReentrantReadWriteLock
接口ReadWriteLock的具體實現,特色:基於讀鎖時,其餘線程能夠進行讀操做,基於寫鎖時,其餘線程讀、寫操做都禁止。
讀寫分離模式
經過讀寫鎖機制,分別向數據容器Map中寫入數據和讀取數據,以此驗證讀寫鎖機制。
public class LockAPI03 { public static void main(String[] args) throws Exception { DataMap dataMap = new DataMap() ; Thread read = new Thread(new GetRun(dataMap)) ; Thread write = new Thread(new PutRun(dataMap)) ; write.start(); Thread.sleep(2000); read.start(); } } class GetRun implements Runnable { private DataMap dataMap ; public GetRun (DataMap dataMap){ this.dataMap = dataMap ; } @Override public void run() { System.out.println("GetRun:"+dataMap.get("myKey")); } } class PutRun implements Runnable { private DataMap dataMap ; public PutRun (DataMap dataMap){ this.dataMap = dataMap ; } @Override public void run() { dataMap.put("myKey","myValue"); } } class DataMap { Map<String,String> dataMap = new HashMap<>() ; ReadWriteLock rwLock = new ReentrantReadWriteLock() ; Lock readLock = rwLock.readLock() ; Lock writeLock = rwLock.writeLock() ; // 讀取數據 public String get (String key){ readLock.lock(); try{ return dataMap.get(key) ; } finally { readLock.unlock(); } } // 寫入數據 public void put (String key,String value){ writeLock.lock(); try{ dataMap.put(key,value) ; System.out.println("執行寫入結束..."); Thread.sleep(10000); } catch (Exception e) { System.out.println("Exception..."); } finally { writeLock.unlock(); } } }
說明:當put方法一直在睡眠狀態時,由於寫鎖的排它性質,因此讀方法是沒法執行的。
LockSupport簡介
LockSupprot定義一組公共靜態方法,這些方法提供最基本的線程阻塞和喚醒功
能。
基礎方法
park():當前線程阻塞,當前線程被中斷或調用unpark方法,park()方法中返回;
park(Object blocker):功能同park(),傳入Object對象,記錄致使線程阻塞的阻塞對象,方便問題排查;
parkNanos(long nanos):指定時間nanos內阻塞當前線程,超時返回;
unpark(Thread thread):喚醒指定處於阻塞狀態的線程;
代碼案例
該流程在購物APP上很是常見,當你準備支付時放棄,會有一個支付失效,在支付失效期內能夠隨時回來支付,過時後須要從新選取支付商品。
public class LockAPI04 { public static void main(String[] args) throws Exception { OrderPay orderPay = new OrderPay("UnPaid") ; Thread orderThread = new Thread(orderPay) ; orderThread.start(); Thread.sleep(3000); orderPay.changeState("Pay"); LockSupport.unpark(orderThread); } } class OrderPay implements Runnable { // 支付狀態 private String orderState ; public OrderPay (String orderState){ this.orderState = orderState ; } public synchronized void changeState (String orderState){ this.orderState = orderState ; } @Override public void run() { if (orderState.equals("UnPaid")){ System.out.println("訂單待支付..."+orderState); LockSupport.park(orderState); } System.out.println("orderState="+orderState); System.out.println("訂單準備發貨..."); } }
這裏基於LockSupport中park和unpark控制線程狀態,實現的等待通知機制。
GitHub·地址 https://github.com/cicadasmile/java-base-parent GitEE·地址 https://gitee.com/cicadasmile/java-base-parent
推薦文章:併發編程系列
序號 | 文章標題 |
---|---|
01 | Java併發:線程的建立方式,狀態週期管理 |
02 | Java併發:線程核心機制,基礎概念擴展 |
03 | Java併發:多線程併發訪問,同步控制 |
04 | Java併發:線程間通訊,等待/通知機制 |
05 | Java併發:悲觀鎖和樂觀鎖機制 |