Java併發編程(06):Lock機制下API用法詳解

本文源碼:GitHub·點這裏 || GitEE·點這裏java

1、Lock體系結構

一、基礎接口簡介

Lock加鎖相關結構中涉及兩個使用普遍的基礎API:ReentrantLock類和Condition接口,基本關係以下:git

Java併發編程(06):Lock機制下API用法詳解

Lock接口github

Java併發編程中資源加鎖的根接口之一,規定了資源鎖使用的幾個基礎方法。面試

ReentrantLock類編程

實現Lock接口的可重入鎖,即線程若是得到當前實例的鎖,並進入任務方法,在線程沒有釋放鎖的狀態下,能夠再次進入任務方法,特色:互斥排它性,即同一個時刻只有一個線程進入任務。多線程

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的順序打印問題,基本思路就是基於線程的等待通知機制,可是實現方式不少,上述只是其中一種方式。

2、讀寫鎖機制

一、基礎API簡介

重入鎖的排它特性決定了性能會產生瓶頸,爲了提高性能問題,JDK中還有另外一套讀寫鎖機制。讀寫鎖中維護一個共享讀鎖和一個排它寫鎖,在實際開發中,讀的場景仍是偏多的,因此讀寫鎖能夠很好的提升併發性。

讀寫鎖相關結構中兩個基礎API:ReadWriteLock接口和ReentrantReadWriteLock實現類,基本關係以下:

Java併發編程(06):Lock機制下API用法詳解

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方法一直在睡眠狀態時,由於寫鎖的排它性質,因此讀方法是沒法執行的。

3、基礎工具類

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控制線程狀態,實現的等待通知機制。

4、源代碼地址

GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

Java併發編程(06):Lock機制下API用法詳解

推薦文章:併發編程系列

序號 文章標題
01 Java併發:線程的建立方式,狀態週期管理
02 Java併發:線程核心機制,基礎概念擴展
03 Java併發:多線程併發訪問,同步控制
04 Java併發:線程間通訊,等待/通知機制
05 Java併發:悲觀鎖和樂觀鎖機制
相關文章
相關標籤/搜索