可重入锁ReentrantLock的原理及使用

参考B站视频学习:https://www.bilibili.com/video/BV1yT411H7YK

一、ReentrantLock的特性

ReentrantLock 是Java中的一个可重入锁(Reentrant Lock),它提供了比内置的synchronized关键字更多的灵活性和功能。它是基于AQS(AbstractQueuedSynchronizer)实现的,可以用于实现更复杂的同步需求。

与内置的synchronized关键字相比,ReentrantLock提供了更多的特性和控制选项。下面我们将介绍ReentrantLock的一些主要特点:

  1. 可重入性:ReentrantLock支持线程的重入,即同一个线程可以多次获取同一个锁而不会被阻塞。这种机制可以避免死锁,并允许线程在持有锁的情况下递归调用同步方法。

  2. 公平性和非公平性:ReentrantLock可以以公平或非公平的方式来获取锁。在公平模式下,锁会按照线程的请求顺序分配,遵循先进先出的原则。而在非公平模式下,新到来的线程有机会插队获取锁,提高了吞吐量。

  3. 可中断性:ReentrantLock支持可中断的获取锁操作。当一个线程在等待锁的过程中,可以通过调用lockInterruptibly()方法来响应中断信号,从而提前结束等待。

  4. 超时获取锁:ReentrantLock提供了tryLock()方法,它可以尝试获取锁,如果锁当前不可用,则立即返回结果。通过设置超时时间,可以避免线程长时间等待。

  5. 条件变量:ReentrantLock提供了条件变量(Condition)的支持,可以通过newCondition()方法创建一个条件变量。条件变量可以让线程在某个条件满足时等待或唤醒,从而更灵活地控制线程的等待和通知。

使用ReentrantLock的基本模式如下:

import java.util.concurrent.locks.ReentrantLock;

public class Example {
    private ReentrantLock lock = new ReentrantLock();

    public void doSomething() {
        lock.lock();
        try {
            // 临界区代码
        } finally {
            lock.unlock();
        }
    }
}

在上面的示例中,我们创建了一个名为Example的类,其中包含一个ReentrantLock实例。在doSomething()方法中,我们首先调用lock()方法获取锁,然后执行临界区代码,最后在finally块中调用unlock()方法释放锁。

ReentrantLock是一个强大而灵活的同步工具,它提供了更多的控制选项和功能,适用于各种复杂的同步需求。然而,由于其使用相对复杂,需要手动管理锁的获取和释放,因此在使用时需要谨慎,并确保正确处理锁的获取和释放,以避免死锁和其他并发问题。

二、ReentrantLock的实现原理

ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似

构造方法接受一个可选的公平参数(默认非公平锁),当设置为true时,表示公平锁,否则为非公平锁。公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。

查看ReentrantLock源码中的构造方法:

public class ReentrantLock implements Lock, java.io.Serializable {
    private final Sync sync;

    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

    // 内部类,用于实现同步逻辑
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // ...
    }

    // 非公平锁的实现
    static final class NonfairSync extends Sync {
        // ...
    }

    // 公平锁的实现
    static final class FairSync extends Sync {
        // ...
    }
}

提供了两个构造方法,不带参数的默认为非公平

如果使用带参数的构造函数,并且传的值为true,则是公平锁

其中NonfairSyncFairSync这两个类父类都是Sync,分别实现了非公平锁和公平锁的逻辑。

而Sync的父类是AQS,所以可以得出ReentrantLock底层主要实现就是基于AQS来实现的

工作流程

在这里插入图片描述

  1. 线程来抢锁后使用CAS的方式修改state状态,修改状态成功为1,则让exclusiveOwnerThread属性指向当前线程,获取锁成功
  2. 假如修改状态失败,则会进入双向队列中等待,head指向双向队列头部,tail指向双向队列尾部
  3. exclusiveOwnerThread为null的时候,则会唤醒在双向队列中等待的线程
  4. 公平锁则体现在按照先后顺序获取锁,非公平体现在不在排队的线程也可以抢锁

synchronized和Lock有什么区别 ?

  • 语法层面
    • synchronized 是关键字,源码在 jvm 中,用 c++ 语言实现
    • Lock 是接口,源码由 jdk 提供,用 java 语言实现
    • 使用 synchronized 时,退出同步代码块锁会自动释放,而使用 Lock 时,需要手动调用 unlock 方法释放锁
  • 功能层面
    • 二者均属于悲观锁、都具备基本的互斥、同步、锁重入功能
    • Lock 提供了许多 synchronized 不具备的功能,例如获取等待状态、公平锁、可打断、可超时、多条件变量
    • Lock 有适合不同场景的实现,如 ReentrantLock, ReentrantReadWriteLock
  • 性能层面
    • 在没有竞争时,synchronized 做了很多优化,如偏向锁、轻量级锁,性能不赖
    • 在竞争激烈时,Lock 的实现通常会提供更好的性能

三、可中断性、超时获取锁和条件变量

下面是一个演示ReentrantLock可中断性、超时获取锁和条件变量功能的示例代码

public class ReentrantLockDemo {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                lock.lockInterruptibly(); // 可中断获取锁
                System.out.println("Thread 1 acquired the lock");
                Thread.sleep(5000); // 模拟线程执行任务
            } catch (InterruptedException e) {
                System.out.println("Thread 1 was interrupted");
            } finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(() -> {
            try {
                if (lock.tryLock(2, TimeUnit.SECONDS)) { // 尝试获取锁
                    System.out.println("Thread 2 acquired the lock");
                    Thread.sleep(3000); // 模拟线程执行任务
                } else {
                    System.out.println("Thread 2 failed to acquire the lock");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        });

        Thread t3 = new Thread(() -> {
            try {
                lock.lock();
                System.out.println("Thread 3 acquired the lock");
                condition.await(); // 等待条件满足
                System.out.println("Thread 3 resumed execution");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        t1.start();
        t2.start();
        t3.start();

        // 主线程等待一段时间后中断t1线程
        try {
            Thread.sleep(2000);
            t1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 唤醒t3线程
        try {
            Thread.sleep(4000);
            lock.lock();
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

在上述示例中,我们创建了一个ReentrantLock实例和一个Condition实例,用于演示可中断性和条件变量的功能。我们创建了三个线程t1t2t3,分别演示不同的功能。

  • t1线程使用lockInterruptibly()方法来获取锁,并在获取锁后休眠5秒钟。在主线程等待2秒后,我们中断了t1线程,它会捕获到InterruptedException并输出相应的信息。

  • t2线程使用tryLock(2, TimeUnit.SECONDS)方法来尝试获取锁,超时时间设置为2秒。如果在超时时间内成功获取到锁,则执行任务;否则输出获取锁失败的信息。

  • t3线程使用lock()方法获取锁,并调用await()方法等待条件满足。在主线程等待4秒后,我们通过调用signal()方法唤醒了t3线程,它会继续执行后续的任务。

这个示例展示了ReentrantLock的可中断性、超时获取锁和条件变量的功能。通过使用这些特性,我们可以更灵活地控制线程的等待和通知,以及避免线程长时间等待或阻塞。

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

abcccccccccccccccode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值