JUC中的 ReentrantLock

Posted XeonYu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC中的 ReentrantLock相关的知识,希望对你有一定的参考价值。

上一篇:

java.util.concurrent.locks包中的接口和实现类

ReentrantLock (可重入锁)

可重入锁就是当一个线程已经获取到锁,可以再次获取这个锁,注意是同一个线程,synchronized也是可重入锁,文章末尾会在代码中看一下这个概念。

ReentrantLock 跟synchronized不同,synchronized是自动上锁,自动解锁的,而Lock接口的实现类是需要手动上锁,手动解锁。

先看一下ReentrantLock中的方法,常用的其实就是对Lock接口实现的方法。


我们先来简单用一用,还是之前买票的例子:

lock()

获取锁,如果锁被其他线程持有,则处于阻塞状态,一直等待直到获取锁


class Ticket {
    public static int totalCount = 30;
    private static int number = 1;
    /*重入锁*/
    private final static ReentrantLock reentrantLock = new ReentrantLock();

    /*卖票*/
    public static void sale() {

        /*获取锁*/
        reentrantLock.lock();

        try {
            if (totalCount == 0) {
                System.out.println("票卖完了");
                return;
            }
            totalCount--;
            System.out.println(Thread.currentThread().getName() + "卖出了第" + number + "张票,剩余" + totalCount + "张票");
            number++;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /*释放锁*/
            reentrantLock.unlock();
        }
    }
}

main方法如下:

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                while (Ticket.totalCount > 0) {
                    Ticket.sale();
                }
            }, "窗口" + i).start();
        }
    }

看下运行结果:

可以看到,数据是ok的,但是只有一个线程在工作,其他线程都没有获取到锁,我们之前用synchronized的时候,是在加锁之前使用了sleep来让其他线程能拿到锁,得到执行机会。
但是用ReentrantLock就不用这样写了,来看下ReentrantLock的构造方法:

两个构造,其中一个构造方法接受一个布尔值,如果传入true,则表示当前锁是一个公平锁,false就是非公平锁

公平锁:
获取锁是按照加锁的顺序来获取的,排队,永远是队列的第一位获取锁。

非公平锁:
获取锁是抢占机制,可能出现一个线程一直获取锁,其他线程一直获取不到锁的现象。默认的是非公平锁

我们把上面的代码的构造中传入true再来看一下:

运行结果如下:

可以看到,每个线程都得到了执行机会。


tryLock()

尝试立刻获取锁,如果到了就返回true,否则就返回false。跟lock方法的区别就是不会一直等待下去,获取不到锁就可以做一些其他操作。

举个例子,去Tony老师店里剪头发,如果去了不用排队就能剪,那就直接剪,如果去了发现tony正在给别人剪,那就不剪了,下次再剪。

简单用一用:

class Tony {

    private final ReentrantLock reentrantLock = new ReentrantLock();

    /*剪头发*/
    public void cutHair() {

        String name = Thread.currentThread().getName();
        System.out.println(name + "进店剪头发");
        if (reentrantLock.tryLock()) {
            try {
                System.out.println(name + "排上队了,开始剪头发。。。");
                TimeUnit.SECONDS.sleep(3);
                System.out.println(name + "头发剪完了,美滋滋。。。");

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                /*释放锁*/
                reentrantLock.unlock();
            }

        } else {
            /*没获取到锁*/
            System.out.println(name + "看到tony老师正在给别人剪头发,很难受,不等了,下次再剪");
        }
    }
}

main方法:

    public static void main(String[] args) {

        Tony tony = new Tony();

        new Thread(tony::cutHair, "yzq").start();
        new Thread(tony::cutHair, "xeon").start();

    }

运行结果如下:


tryLock(long time, TimeUnit unit)

跟tryLock类似,只是多了个获取锁的超时时间,在指定时间段内获取到锁就返回true,否则返回false。
还是上面那个例子,去Tony老师店里剪头发,如果去了不用排队就能剪,那就直接剪,如果去了发现tony正在给别人剪,那就等2秒钟,2秒钟还没剪完,那就不等了,下次再剪。

class Tony {

    private final ReentrantLock reentrantLock = new ReentrantLock();

    /*剪头发*/
    public void cutHair() {

        String name = Thread.currentThread().getName();
        System.out.println(name + "进店剪头发");
        try {
            if (reentrantLock.tryLock(2, TimeUnit.SECONDS)) {
                System.out.println(name + "排上队了,开始剪头发。。。");
                TimeUnit.SECONDS.sleep(3);
                System.out.println(name + "头发剪完了,美滋滋。。。");

            } else {
                /*没获取到锁*/
                System.out.println(name + "看到tony老师正在给别人剪头发,等了2秒钟还没剪完那就不等了,下次再剪");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            /*判断是否是当前线程持有锁  是的话释放  否则不用处理*/
            if (reentrantLock.isHeldByCurrentThread()) {
                reentrantLock.unlock();
            }

        }
    }
}

唯一需要注意的是在finally 中释放锁的话,需要加个判断,是否是当前线程持有锁 :isHeldByCurrentThread,是的话才能释放锁,否则会报
IllegalMonitorStateException异常。

运行结果:

好了 ReentrantLock常用的方法就是这些。

代码理解什么是可重入锁

文章开头我们说了可重入锁的概念,这里我们用代码看一下 可重入
是如何体现出来的。

代码如下:

class MyReentrantLock {
    private final ReentrantLock lock = new ReentrantLock();

    public void fun1() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());
            fun2();
            System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());

        }
    }

    public void fun2() {
        lock.lock();
        try {
            TimeUnit.SECONDS.sleep(1);
            System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + "持有当前锁的次数:" + lock.getHoldCount());
        }

    }
}

上面两个方法,每个方法都有加锁解锁的过程,且fun1中调用了fun2,getHoldCount方法用来查询当先线程持有当前锁的次数

来看下main方法

    public static void main(String[] args) {

        MyReentrantLock myReentrantLock = new MyReentrantLock();

        new Thread(myReentrantLock::fun1, "线程1").start();

        new Thread(myReentrantLock::fun2, "线程2").start();
    }

起了两个线程,分别调用fun1和fun2.看下运行结果:

可以看到,线程1调用的是fun1,先在fun1中加了一次锁,然后又调用了fun2,在fun2中又加了一次锁,所以,线程1加锁的次数是两次。直到线程1把2次锁全部释放后,线程2才有机会执行。

总结一下:同一个线程可以多次获取同一个锁,这个锁就是可重入锁


如果你觉得本文对你有帮助,麻烦动动手指顶一下,可以帮助到更多的开发者,如果文中有什么错误的地方,还望指正,转载请注明转自喻志强的博客 ,谢谢!

以上是关于JUC中的 ReentrantLock的主要内容,如果未能解决你的问题,请参考以下文章

重点知识学习(8.3)--[JUC常用类 || Java中的14把锁 || 对象头 || Synchronized 与 ReentrantLock]

JUC并发编程 共享模式之工具 JUC ReentrantLock -- ReentrantLock原理

AQS系列- ReentrantLock的加锁

Java——聊聊JUC中的锁(synchronized & Lock & ReentrantLock)

Java——聊聊JUC中的锁(synchronized & Lock & ReentrantLock)

Java——聊聊JUC中的锁(synchronized & Lock & ReentrantLock)