ReentrantLock(可重入锁)

举报
别团等shy哥发育 发表于 2023/10/18 14:49:31 2023/10/18
【摘要】 ReentrantLock(可重入锁) ReentrantLock了解吗?是公平锁吗?ReentrantLock(可重入锁)实现了Lock接口,是一个可重入且独占式的锁,和synchronized关键字类似,不过ReentrantLock更灵活、强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。重入锁指在同一线程中,外部方法获得锁之后,内层递归方法依然可以获得该锁,如果锁不具备重入...

ReentrantLock(可重入锁)

ReentrantLock了解吗?是公平锁吗?

ReentrantLock(可重入锁)实现了Lock接口,是一个可重入且独占式的锁,和synchronized关键字类似,不过ReentrantLock更灵活、强大,增加了轮询、超时、中断、公平锁和非公平锁等高级功能。

重入锁指在同一线程中,外部方法获得锁之后,内层递归方法依然可以获得该锁,如果锁不具备重入性,那么当同一个线程两次获取锁的时候就会发生死锁。

独占锁指该锁在同一时刻只能被一个线程获取,而获取锁的其他线程只能在同步队列中等待。

ReentrantLock默认使用非公平锁,也可以通过构造器显式指定公平锁。

  • 公平锁:锁被释放之后,先申请的线程先得到锁。性能较差,公平锁为了保证时间上的绝对顺序,上下文切换更频繁。
  • 非公平锁:锁被释放之后,后申请的线程可能会先获取到锁,是随机或者按照其他优先级排序的。性能更好,但可能会导致某些线程无法获取到锁。

synchronized与ReentrantLock的异同

  • 两者都是可重入锁

  • synchronized依赖于JVMReentrantLock依赖于API,synchronized是依赖于JVM的,而ReentrantLock是JDK层面实现的也就是API层面,需要lock()unlock()方法配合try/finally语句块来完成。

  • synchronized不需要用户手动释放锁,ReentrantLock则需要用户手动释放锁。

  • ReentrantLocksynchronized增加了一些高级功能:

    等待可中断:ReentrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。

    可实现公平锁: ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过ReentrantLock类的ReentrantLock(boolean fair)构造方法来指定是否是公平的。

    可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

ReentrantLock如何避免死锁:响应中断、可轮询锁、定时锁

(1)响应中断:在synchronized中如果有一个线程尝试获取一把锁,则其结果是要么获取锁继续执行,要么继续等待。ReentrantLock还提供了可响应中断的可能,即在等待锁的过程中,线程可以按需取消对锁的请求。

(2)可轮询锁:通过boolean tryLock()获取锁。如果有可用锁,则获取该锁并返回true,如果无可用锁,则立即返回false。

(3)定时锁:通过boolean tryLock(long time,TimeUnit unit) throws InterruptedException获取锁。如果在指定的时间内获取到了可用锁,且当前线程未被中断,则获取该锁并返回true。如果在指定的时间内获取不到可用锁,则将禁用当前线程,并且在发生如下三种情况之前,该线程一直处于休眠状态。

  • 当前线程获取到了可用锁并返回true。
  • 在当前线程进入此方法时若设置了该线程的中断状态,或者当前线程在获取锁时被中断,则将抛出InterruptedException,并清除当前线程的已中断状态。
  • 当前线程获取锁的时间超过了指定的等待时间,将返回false。如果设定的时间小于或等于0,则该方法将完全不等待。

ReentrantLock抢占锁的三种方法

  • lock()方法用于阻塞抢锁,抢不到锁时线程会一直阻塞。
  • tryLock()方法用于尝试抢锁,该方法有返回值,如果成功就返回true,如果失败(锁已被其他线程获取)就返回false。此方法无论如何都会立即返回,在抢不到锁时,线程不会像调用lock()方法那样一直被阻塞。
  • tryLock(long time,TimeUnit unit)方法和tryLock()方法类似,只不过这个方法在抢不到锁时会阻塞一段时间。如果在阻塞期间获取到锁就立即返回true,超时则返回false

(1)使用lock()方法

public void lock()

模板代码如下:

ReentrantLock lock = new ReentrantLock();
lock.lock();    //1:抢占锁
try {
    //2:抢锁成功,执行临界区代码
} finally {
    lock.unlock();//3:释放锁
}

注意:

  • 释放锁操作lock.unlock()必须在try-catch结构的finally块中执行,否则,如果临界区代码抛出异常,锁就有可能永远得不到释放。

  • 抢占锁操作lock.lock()必须在try语句块之外,而不是放在try语句块之内。

    原因一:lock()方法没有声明抛出异常,所以可以不包含到try块中。

    原因二:lock()方法并不一定能够抢占锁成功,如果没有抢占成功,当然也就不需要释放锁,而且在没有占有锁的情况下去释放锁,可能会导致运行时异常。

  • 在抢占锁操作lock.lock()和try语句之间不要插入任何代码,避免抛出异常而导致释放锁操作lock.unlock()执行不到,导致锁无法被释放。

(2)调用tryLock()方法非阻塞抢锁

public boolean tryLock()

lock()是阻塞式抢占,在没有抢到锁的情况下,当前线程会阻塞。

tryLock()是非阻塞式抢占,在没有抢到锁的情况下,当前线程会立即返回,不会被阻塞。

//创建锁对象
ReentrantLock lock = new ReentrantLock();
if(lock.tryLock()){//1:尝试抢占锁
    try {
        //2:抢锁成功,执行临界区代码
    } finally {
        lock.unlock();  //3:释放锁
    }
}else{
    //4:抢锁失败,执行后备动作
}

(3)调用tryLock(long time,TimeUnit unit)方法抢锁

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException

tryLock(long timeout, TimeUnit unit) throws InterruptedException方法用于限时抢锁,该方法在抢锁时会进行一段时间的阻塞等待,其中的time参数代表最大的阻塞时长,unit参数为时长的单位。

//创建锁对象
ReentrantLock lock = new ReentrantLock();
try {
    if(lock.tryLock(1, TimeUnit.SECONDS)){//1:尝试抢占锁
        try {
            //2:抢锁成功,执行临界区代码
        } finally {
            lock.unlock();  //3:释放锁
        }
    }else{
        //4:限时抢锁失败,执行后备动作
    }
} catch (InterruptedException e) {
   e.printStackTrace();
}

Condition

与Object对象的waitnotify两类方法类似,基于Lock显式锁,JUC也提供了一个用于线程间进行“等待-通知”方式实现的接口java.util.concurrent.locks.Condition

(1)Lock接口的主要方法

public interface Condition{
    //方法1:等待,使当前线程加入等待队列中,并释放当前锁
    //当其他线程调用signal()时,等待队列中的某个线程会被唤醒,重新去抢锁
    void await() throws InterruptedException;
    
    //方法2:通知。此方法在功能上与Object.notify()语义等效
    //唤醒一个在await()等待队列中的线程
    void signal();
    
    //方法3:通知全部。唤醒await()等待队列中所有的线程,此方法与Object.notifyAll()语义上等效
    void signalAll();
    
    //方法3:限时等待。此方法与await()语义上等效
    //不同点在于,在指定time等待超时后,如果没有被唤醒,线程将中止等待
    //线程等待超时返回false,其他情况返回true
	boolean await(long time, TimeUnit unit) throws InterruptedException
}

Condition对象的signal(通知)方法和同一个对象的await(等待)方法是一一配对使用的,也就是说,一个Condition对象的signal(或signalAll)方法不能去唤醒其他Condition对象上的await线程。

Condition对象是基于显式锁的,所以不能独立创建一个Condition对象,而是需要借助于显式锁实例去获取其绑定的Condition对象。

不过,每一个Lock显式锁实例都可以有任意数量的Condition对象。具体来说,可以通过lock.newCondition()方法去获取一个与当前显式锁绑定的Condition实例,然后通过该Condition实例进行“等待-通知”方式的线程间通信。

public class ReentrantLockCondition {
    //创建一个显式锁
    static Lock lock=new ReentrantLock();
    //获取一个显式锁绑定的Condition对象
    static private Condition condition=lock.newCondition();

    //等待线程执行异步目标任务
    static class WaitTarget implements Runnable{
        @Override
        public void run() {
            lock.lock();//1:抢占锁
            try {
                System.out.println("我是等待方");
                condition.await();//2:开始等待,并且释放锁
                System.out.println("收到通知,等待方继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();//释放锁
            }

        }
    }
    //通知线程的异步目标任务
    static class NotifyTarget implements Runnable{
        @Override
        public void run() {
            lock.lock(); //3:抢锁
            try {
                System.out.println("我是通知方");
                condition.signal(); //4:发送通知
                System.out.println("发出通知了,但是线程还没有立马释放锁");
            } finally {
                lock.unlock();  //5:释放锁之后,等待线程才能获得锁
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //创建等待线程
        Thread waitThread = new Thread(new WaitTarget(), "WaitThread");
        //启动等待线程
        waitThread.start();
        Thread.sleep(2000);//等待一会

        //创建通知线程
        Thread notifyThread = new Thread(new NotifyTarget(), "NotifyThread");
        //启动通知线程
        notifyThread.start();
    }
}

image-20230828153124700

高级篇:基于Condition的“等待-通知”机制实现一个生产者-消费者程序。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。