美文网首页一些收藏
ReentrantLock可重入锁的使用

ReentrantLock可重入锁的使用

作者: Djbfifjd | 来源:发表于2020-09-01 11:34 被阅读0次

    Synchronized和lock区别

    何为可重入锁?当一个线程获得了当前实例的锁,并进入方法a,这个线程在没有释放这把锁的时候,可以再次进入方法a。

    一、简介

    Java 除了使用关键字 synchronized 外,还可以使用 ReentrantLock 实现排他锁(Exclusive Locks)的功能。而且 ReentrantLock 相比 synchronized 而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。ReentrantLock 常常对比着 synchronized 来分析,先对比着来看然后再一点一点分析。

    1️⃣synchronized 是排他锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock 也是排他锁锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
    2️⃣synchronized 可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁。ReentrantLock 也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
    3️⃣synchronized 不可响应中断,一个线程获取不到锁就一直等着。ReentrantLock 可以响应中断。

    synchronized 所没有的,一个最主要的就是 ReentrantLock 可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。

    二、使用

    1️⃣简单使用:一个最基础的使用案例,也就是实现锁的功能。

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReentrantLockDemo {
        private static final Lock lock = new ReentrantLock();
        public static void main(String[] args) {
            new Thread(() -> test(), "线程A").start();
            new Thread(() -> test(), "线程B").start();
        }
        public static void test() {
            for (int i = 0; i < 2; i++) {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "获取了锁");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    

    这里定义了一个 ReentrantLock,然后在 test 方法中分别 lock 和 unlock,运行一遍就可以实现功能。这就是最简单的功能实现,代码很简单。再看看 ReentrantLock 和 synchronized 不一样的地方,那就是公平锁的实现。

    2️⃣公平锁实现
    对于公平锁的实现,要结合可重入性质。

    public class ReentrantLockDemo {
        private static final Lock lock = new ReentrantLock(true);
        public static void main(String[] args) {
            new Thread(() -> test(), "线程A").start();
            new Thread(() -> test(), "线程B").start();
            new Thread(() -> test(), "线程C").start();
            new Thread(() -> test(), "线程D").start();
            new Thread(() -> test(), "线程E").start();
        }
        public static void test() {
            for (int i = 0; i < 2; i++) {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "获取了锁");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    
    new 一个 ReentrantLock 的时候参数为 true,表明实现公平锁机制。在这里多定义几个线程ABCDE,然后在 test 方法中循环执行了两次加锁和解锁的过程:

    3️⃣非公平锁实现

    非公平锁就是随机的获取,谁运气好,cpu时间片轮到哪个线程,那个线程就能获取锁。和公平锁的区别很简单,就在于先 new 一个 ReentrantLock 的时候参数为 false,当然也可以不写,默认就是 false。测试结果:

    4️⃣响应中断
    响应中断就是一个线程获取不到锁,不会傻傻的一直等下去,ReentrantLock 会给予一个中断回应。在这里举一个死锁的案例:

    public class ReentrantLockDemo {
        private static final Lock lockA = new ReentrantLock();
        private static final Lock lockB = new ReentrantLock();
    
        public static void main(String[] args) {
            Thread threadA = new Thread(new ThreadDemo(lockA, lockB));
            Thread threadB = new Thread(new ThreadDemo(lockA, lockB));
            threadA.start();
            threadB.start();
            threadA.interrupt();//第一个线程中断
        }
    
        static class ThreadDemo implements Runnable {
            Lock firstLock;
            Lock secondLock;
            public ThreadDemo(Lock firstLock, Lock secondLock) {
                this.firstLock = firstLock;
                this.secondLock = secondLock;
            }
            @Override
            public void run() {
                try {
                    firstLock.lockInterruptibly();
                    TimeUnit.MINUTES.sleep(50);
                    secondLock.lockInterruptibly();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    firstLock.unlock();
                    secondLock.unlock();
                    System.out.println(Thread.currentThread().getName() + "获取到了资源,正常结束!");
                }
            }
        }
    }
    
    在这里定义了两个锁 lockA 和 lockB。然后使用两个线程 threadA 和 threadB 构造死锁场景。正常情况下,这两个线程相互等待获取资源而处于死循环状态。但是此时 threadA 中断,另外一个线程就可以获取资源,正常地执行了。测试结果:

    5️⃣限时等待
    通过tryLock方法来实现,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true 表示获取锁成功,false 表示获取锁失败。可以将这种方法用来解决死锁问题。首先还是测试代码,不过在这里不需要再去中断其中的线程了,直接看线程类是如何实现的:

    static class ThreadDemo implements Runnable {
        Lock firstLock;
        Lock secondLock;
    
        public ThreadDemo(Lock firstLock, Lock secondLock) {
            this.firstLock = firstLock;
            this.secondLock = secondLock;
        }
    
        @Override
        public void run() {
            try {
                if (!lockA.tryLock()) {
                    TimeUnit.MILLISECONDS.sleep(10);
                }
                if (!lockB.tryLock()) {
                    TimeUnit.MILLISECONDS.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                firstLock.unlock();
                secondLock.unlock();
                System.out.println(Thread.currentThread().getName() + "正常结束!");
            }
        }
    }
    

    在这个案例中,一个线程获取 lockA 时候第一次失败,那就等10毫秒之后第二次获取,就这样一直不停的调试,一直等到获取到相应的资源为止。当然,可以设置 tryLock 的超时等待时间 tryLock(long timeout,TimeUnit unit),也就是说一个线程在指定的时间内没有获取锁,那就会返回 false,就可以再去做其他事了。

    相关文章

      网友评论

        本文标题:ReentrantLock可重入锁的使用

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