Java中的可重入锁

作者: 小草莓子桑 | 来源:发表于2017-06-25 23:18 被阅读0次

    在前面ConcurrentHashMap的实现原理与使用(二)中提到了可重入锁ReentrantLock,说有时间再聊,这几天下大雨,《变5》也没有看成,就来和大家一起聊聊Java中的可重入锁。

    synchronized与ReentrantLock

    Java官方API中粘过来说明:A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.在这里翻一下(英文不好,强行使用百度翻译加上自己组织):一个可重入的互斥锁,和关键词synchronized隐式锁修饰的方法与语句(可能翻译错了,就理解为和synchronized具有相同作用吧)具有相同的功能和语义,但具有扩展功能,翻译完毕。

    通俗来讲可重入锁是一个线程在获取到一个锁以后,再次获取该锁线程不会被阻塞,synchronized是隐式的进行加锁,而ReentrantLock不是隐式的,但是,他们两的功能和语义基本相同,都是可重入锁,下面举个栗子来证明synchronized、ReentrantLock是可重入锁吧。

    先来个synchronized可重入的栗子
    package edu.thread.reentrantLock;
    
    /**
     * @Description: .
     * @Author: ZhaoWeiNan .
     * @CreatedTime: 2017/6/25 .
     * @Version: 1.0 .
     */
    public class SynchronizedTest extends Thread {
    
        private String type;
    
        public SynchronizedTest(String type,String name){
            super(name);
            this.type = type;
        }
    
        @Override
        public void run() {
            if ("死锁".equals(type)){
                //死锁栗子
                DeadLock();
            }else if ("可重入".equals(type)){
                //可重入栗子
                ReentrantLock();
            }
        }
    
        /**
         *  一个阻塞的栗子。
         *  1.老公线程,先用西瓜加锁,拿到锁以后,再去用西瓜刀加锁。
         *  2.此时老婆线程已经获取到西瓜刀的锁,然后sleep了。
         *  3.老公线程获取不到西瓜刀的锁,所以被阻塞。
         */
        private void DeadLock(){
            if (Thread.currentThread().getName().equals("老公")){
                synchronized ("西瓜"){
                    System.out.println("老公买了西瓜,准备去拿西瓜刀。");
                    synchronized("西瓜刀"){
                        System.out.println("老公拿了西瓜刀,准备吃西瓜。");
                    }
    
                }
            }else if (Thread.currentThread().getName().equals("老婆")){
                synchronized ("西瓜刀"){
                    System.out.println("老婆把西瓜刀藏起来了,不让老公吃西瓜。");
                    try {
                        Thread.sleep(1000000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        /**
         * 可重入的栗子。
         * 1.老公线程获取了徒手吃西瓜的锁,注意此时,徒手吃西瓜的锁并没有被释放。
         * 2.同时start了一个老婆线程了,徒手吃西瓜的锁并没有被释放,所以老婆线程没有获取到徒手吃西瓜锁。
         * 3.老公线程再次获取徒手吃西瓜的锁,没有被阻塞,证明了synchronized的可重入。
         */
        private void ReentrantLock(){
            if (Thread.currentThread().getName().equals("老公")){
                synchronized ("徒手吃西瓜"){
                    System.out.println("老公买了西瓜,徒手掰西瓜。");
                    synchronized("徒手吃西瓜"){
                        System.out.println("老公掰开了西瓜,准备吃西瓜。");
                        System.out.println("老公把老婆锁在外面,吃完西瓜才让她进来。");
                        try {
                            Thread.sleep(1000000000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }else if (Thread.currentThread().getName().equals("老婆")){
                System.out.println("老婆冲进家。");
                synchronized ("徒手吃西瓜"){
                    System.out.println("老婆抓住老公的手,不让老公吃西瓜。");
                }
            }
        }
    }
    
    class Demo{
        public static void main(String[] args){
            //先运行阻塞的栗子
            /*SynchronizedTest lg = new SynchronizedTest("死锁","老公");
            SynchronizedTest lp = new SynchronizedTest("死锁","老婆");
    
            lg.start();
            lp.start();*/
    
            //在运行可重入的栗子
            SynchronizedTest lg = new SynchronizedTest("可重入","老公");
            SynchronizedTest lp = new SynchronizedTest("可重入","老婆");
            lg.start();
            lp.start();
        }
    }
    

    先运行了一个死锁的栗子,其中老婆线程获取了西瓜刀锁,把老公线程阻塞了,老公线程没有迟到西瓜:


    老婆线程获取了西瓜刀锁,阻塞了老公线程

    在运行了一个可重入的栗子,其中老公线程获取了徒手吃西瓜锁,此时没有释放,所以阻塞了老婆线程获取徒手吃西瓜锁,当徒手吃西瓜锁释放的情况下,老公再次获取该锁的时候,没有被阻塞:


    老公线程获取了徒手吃西瓜锁,再次获取该锁时,没有被阻塞
    再来个ReentrantLock可重入的栗子

    改写一下上面那个栗子,使用ReentrantLock实现锁:

    package edu.thread.reentrantLock;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @Description: .
     * @Author: ZhaoWeiNan .
     * @CreatedTime: 2017/6/25 .
     * @Version: 1.0 .
     */
    public class ReentrantLockTest extends Thread {
    
        private String type;
    
        //西瓜锁
        private ReentrantLock lock1;
        //西瓜刀锁
        private ReentrantLock lock2;
        //徒手吃西瓜锁
        private ReentrantLock lock3;
    
        public ReentrantLockTest(String type,String name,ReentrantLock lock1,ReentrantLock lock2){
            super(name);
            this.type = type;
            this.lock1 = lock1;
            this.lock2 = lock2;
        }
    
        public ReentrantLockTest(String type,String name,ReentrantLock lock3){
            super(name);
            this.type = type;
            this.lock3 = lock3;
        }
    
        @Override
        public void run() {
            if ("死锁".equals(type)){
                //死锁栗子
                DeadLock();
            }else if ("可重入".equals(type)){
                //可重入栗子
                ReentrantLock();
            }
        }
    
        /**
         *  一个阻塞的栗子。
         *  1.老公线程,先用西瓜加锁,拿到锁以后,再去用西瓜刀加锁。
         *  2.此时老婆线程已经获取到西瓜刀的锁,然后sleep了。
         *  3.老公线程获取不到西瓜刀的锁,所以被阻塞。
         */
        private void DeadLock(){
            if (Thread.currentThread().getName().equals("老公")){
                lock1.lock();
                System.out.println("老公买了西瓜,准备去拿西瓜刀。");
                lock2.lock();
                System.out.println("老公拿了西瓜刀,准备吃西瓜。");
                lock2.unlock();
                lock1.unlock();
            } else if (Thread.currentThread().getName().equals("老婆")){
                lock2.lock();
                System.out.println("老婆把西瓜刀藏起来了,不让老公吃西瓜。");
                try {
                    Thread.sleep(1000000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock2.unlock();
            }
        }
    
        /**
         * 可重入的栗子。
         * 1.老公线程获取了徒手吃西瓜的锁,注意此时,徒手吃西瓜的锁并没有被释放。
         * 2.同时start了一个老婆线程了,徒手吃西瓜的锁并没有被释放,所以老婆线程没有获取到徒手吃西瓜锁。
         * 3.老公线程再次获取徒手吃西瓜的锁,没有被阻塞,证明了ReentrantLock的可重入。
         */
        private void ReentrantLock(){
            if (Thread.currentThread().getName().equals("老公")){
                lock3.lock();
                System.out.println("老公买了西瓜,徒手掰西瓜。");
                lock3.lock();
                System.out.println("老公掰开了西瓜,准备吃西瓜。");
                System.out.println("老公把老婆锁在外面,吃完西瓜才让她进来。");
                try {
                    Thread.sleep(1000000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock3.unlock();
            }else if (Thread.currentThread().getName().equals("老婆")){
                System.out.println("老婆冲进家。");
                lock3.lock();
                System.out.println("老婆抓住老公的手,不让老公吃西瓜。");
                lock3.unlock();
            }
    
    
        }
    }
    
    class Demo1 {
        public static void main(String[] args){
            //先运行阻塞的栗子
           /* //西瓜锁
            ReentrantLock lock1 = new ReentrantLock();
            //西瓜刀锁
            ReentrantLock lock2 = new ReentrantLock();
            ReentrantLockTest lg = new ReentrantLockTest("死锁","老公",lock1,lock2);
            ReentrantLockTest lp = new ReentrantLockTest("死锁","老婆",lock1,lock2);
    
            lg.start();
            lp.start();*/
    
            //在运行可重入的栗子
            //徒手吃西瓜锁
            ReentrantLock lock3 = new ReentrantLock();
            ReentrantLockTest lg = new ReentrantLockTest("可重入","老公",lock3);
            ReentrantLockTest lp = new ReentrantLockTest("可重入","老婆",lock3);
            lg.start();
            lp.start();
        }
    }
    

    先运行了一个死锁的栗子,其中老婆线程获取了西瓜刀锁,把老公线程阻塞了,老公线程没有迟到西瓜:


    老婆线程获取了西瓜刀锁,阻塞了老公线程

    在运行了一个可重入的栗子,其中老公线程获取了徒手吃西瓜锁,此时没有释放,所以阻塞了老婆线程获取徒手吃西瓜锁,当徒手吃西瓜锁释放的情况下,老公再次获取该锁的时候,没有被阻塞:


    老公线程获取了徒手吃西瓜锁,再次获取该锁时,没有被阻塞

    利用ReentrantLock实现消费者生产者模式

    package edu.thread.reentrantLock;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @Description: .
     * @Author: ZhaoWeiNan .
     * @CreatedTime: 2017/6/25 .
     * @Version: 1.0 .
     */
    public class Demo2 {
    
        public static void main(String[] args){
            Product product = new Product(new ReentrantLock());
            Producer producer = new Producer(product);
            Customer customer = new Customer(product);
            producer.start();
            customer.start();
        }
    }
    
    /**
     * 产品
     */
    class Product{
        //名称
        String name;
        //价格
        int price;
        //可以生产的标识
        boolean flag = false;
        //锁
        ReentrantLock lock;
        //消费条件
        Condition customerCondition;
        //生产条件
        Condition producerCondition;
    
        public Product(ReentrantLock lock) {
            this.lock = lock;
            customerCondition = lock.newCondition();
            producerCondition = lock.newCondition();
        }
    }
    
    /**
     * 消费者
     */
    class Customer extends Thread{
        private Product product;
    
        public Customer(Product product) {
            this.product = product;
        }
    
        @Override
        public void run() {
            while (true){
                try {
                    product.lock.lock();
                    //先判断产品的标识是否可以消费
                    if (product.flag == true){
                        //消费
                        System.out.println("消费了产品");
                        System.out.println("产品为:" + product.name);
                        System.out.println("价格为:" + product.price);
    
                        //消费了产品,把标注改为false
                        product.flag = false;
    
                        //通知在producerCondition上等待的生产者线程进行生产
                        product.producerCondition.signal();
                    }else {
                        //消费者线程在customerCondition上等待
                        product.customerCondition.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    product.lock.unlock();
                }
            }
        }
    }
    
    /**
     * 生产者
     */
    class Producer extends Thread{
    
        private Product product;
    
        public Producer(Product product) {
            this.product = product;
        }
    
        @Override
        public void run() {
            int i = 0;
            while (true){
    
                try {
                    product.lock.lock();
                    //产品标识是false没有生产
                    if (product.flag == false){
                        if (i % 2 == 0){
                            //偶数的时候生产cpu
                            product.name = "CPU";
                            product.price = 2000;
                        }else {
                            //奇数生产内存条
                            product.name = "内存条";
                            product.price = 300;
                        }
                        i ++;
                        System.out.println("生产了产品");
                        System.out.println("产品为:" + product.name);
                        System.out.println("价格为:" + product.price);
                        //把产品标识改为true,可以消费
                        product.flag = true;
                        //通知在customerCondition等待的消费者线程进行消费
                        product.customerCondition.signal();
                    }else {
                        //已经生产了
                        //生产者线程在producerCondition上等待
                        product.producerCondition.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    product.lock.unlock();
                }
            }
        }
    }
    
    运行结果

    说说ReentrantLock中通知机制的使用方法,在ReentrantLock中通知是用Condition来实现的,Condition对象中的signal方法相当于,Obejct对象中的wait方法,Condition对象中的signal方法相当于Object对象中的notify方法,同理notifyAll、signalAll方法,wait(long timeout)、await(long time, TimeUnit unit)方法作用相同。需要注意的一点是,线程是在Condition对象中等待,也是在Condition对象中被唤醒,拿上面的栗子来说:

    //通知在producerCondition上等待的生产者线程进行生产
    product.producerCondition.signal();
    
    //消费者线程在customerCondition上等待
    product.customerCondition.await();
    
     //生产者线程在producerCondition上等待
     product.producerCondition.await();
    
     //通知在customerCondition等待的消费者线程进行消费
     product.customerCondition.signal();
    

    如果让生产者线程在producerCondition等待后,如果,如果调用product.customerCondition.signal(),不会唤醒生产者线程,因为生产者线程是在
    producerCondition对象中等待的,使用的时候需要注意这一点。
    文本中的代码已经上传到开源中国了,有兴趣的小伙伴可以拿去,https://git.oschina.net/zhaoweinan/reentrantlockdemo

    Java中的可重入锁就为大家介绍到这里,欢迎大家来交流,指出文中一些说错的地方,让我加深认识。
    谢谢大家!

    相关文章

      网友评论

        本文标题:Java中的可重入锁

        本文链接:https://www.haomeiwen.com/subject/bcjrcxtx.html