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并发:悲观锁和乐观锁机制
相关文章
相关标签/搜索