zoukankan      html  css  js  c++  java
  • Java并发编程(06):Lock机制下API用法详解

    本文源码:GitHub·点这里 || GitEE·点这里

    一、Lock体系结构

    1、基础接口简介

    Lock加锁相关结构中涉及两个使用广泛的基础API:ReentrantLock类和Condition接口,基本关系如下:

    Lock接口

    Java并发编程中资源加锁的根接口之一,规定了资源锁使用的几个基础方法。

    ReentrantLock类

    实现Lock接口的可重入锁,即线程如果获得当前实例的锁,并进入任务方法,在线程没有释放锁的状态下,可以再次进入任务方法,特点:互斥排它性,即同一个时刻只有一个线程进入任务。

    Condition接口

    Condition接口描述可能会与锁有关联的条件变量,提供了更强大的功能,例如在线程的等待/通知机制上,Conditon可以实现多路通知和选择性通知。

    2、使用案例

    生产消费模式

    写线程向容器中添加数据,读线程从容器获取数据,如果容器为空时,读线程等待。

    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的顺序打印问题,基本思路就是基于线程的等待通知机制,但是实现方式很多,上述只是其中一种方式。

    二、读写锁机制

    1、基础API简介

    重入锁的排它特性决定了性能会产生瓶颈,为了提升性能问题,JDK中还有另一套读写锁机制。读写锁中维护一个共享读锁和一个排它写锁,在实际开发中,读的场景还是偏多的,所以读写锁可以很好的提高并发性。

    读写锁相关结构中两个基础API:ReadWriteLock接口和ReentrantReadWriteLock实现类,基本关系如下:

    ReadWriteLock

    提供两个基础方法,readLock获取读机制锁,writeLock获取写机制锁。

    ReentrantReadWriteLock

    接口ReadWriteLock的具体实现,特点:基于读锁时,其他线程可以进行读操作,基于写锁时,其他线程读、写操作都禁止。

    2、使用案例

    读写分离模式

    通过读写锁机制,分别向数据容器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并发:悲观锁和乐观锁机制
  • 相关阅读:
    线性判别分析(Linear Discriminant Analysis, LDA)算法分析
    OpenCV学习(37) 人脸识别(2)
    OpenCV学习(36) 人脸识别(1)
    OpenCV学习(35) OpenCV中的PCA算法
    PCA的数学原理
    OpenCV学习(34) 点到轮廓的距离
    OpenCV学习(33) 轮廓的特征矩Moment
    OpenCV学习(32) 求轮廓的包围盒
    http://www.cnblogs.com/snake-hand/p/3206655.html
    C++11 lambda 表达式解析
  • 原文地址:https://www.cnblogs.com/cicada-smile/p/13238156.html
Copyright © 2011-2022 走看看