暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

面试题系列:关于可重入锁你了解多少?

1024笔记 2023-07-13
105

作者平台:

| CSDN:https://blog.csdn.net/qq_41153943(ID:江夏、)

| 掘金:https://juejin.cn/user/651387938290686(ID:jiangxia_1024

| 知乎:https://www.zhihu.com/people/1024-paper-96(ID:江夏)

| GitHub:https://github.com/JiangXia-1024?tab=repositories

| 微信公众号:1024笔记

本文一共2712字,预计阅读20分钟
1、什么是可重入和可重入锁
可重入是指一个线程在持有锁的情况下,能够再次获取该锁,而不会发生死锁的情况。可重入锁是一种支持可重入的锁机制。

当一个线程在持有锁的情况下,再次对同一个锁进行请求时,如果该锁是可重入锁,则线程可以再次获取该锁而不会被阻塞。可重入锁会维护一个计数器,用于记录线程对锁的获取次数,每次成功获取锁时,计数器会加1,每次释放锁时,计数器会减1。只有当计数器为0时,其他线程才能获取该锁。

可重入锁的主要作用是避免死锁的发生。在一个线程已经持有锁的情况下,如果该线程再次请求该锁,如果不支持可重入,则会发生死锁,导致程序无法继续执行。可重入锁通过允许一个线程多次获取同一个锁,保证了线程的执行不会被阻塞,从而避免了死锁的问题。

2、可重入锁的原理

可重入锁是指同一个线程可以多次获取同一个锁,而不会发生死锁。其原理主要涉及两个概念:线程标识和锁计数器。

1. 线程标识:每个线程在获取锁时,会将自身的标识与锁关联起来,标识了该线程是锁的拥有者。

2. 锁计数器:每个锁都有一个计数器,用于记录当前线程对该锁的获取次数。初始时计数器为0。

当一个线程第一次获取锁时,它会将自身的标识与锁关联,并将锁计数器加1。此时,该线程成为锁的拥有者,并且可以成功获取锁。

当同一个线程再次获取同一个锁时,它会检查自身的标识是否与锁关联,并且检查锁计数器是否大于0。如果满足这两个条件,说明该线程已经是锁的拥有者,并且可以再次成功获取锁。此时,锁计数器会再次加1。

当同一个线程释放锁时,它会将锁计数器减1。只有当锁计数器为0时,锁才会被完全释放,其他线程才能获取该锁。

通过线程标识和锁计数器,可重入锁实现了同一个线程可以多次获取同一个锁的功能。这样可以避免死锁,并且提高了程序的灵活性和效率。

3、实现可重入锁

根据上面的原理,简单实现一个可重入锁:

    public class ReentrantLock {
    private boolean isLocked = false;
    private Thread lockedBy = null;
    private int lockCount = 0;




    public synchronized void lock() throws InterruptedException {
    Thread currentThread = Thread.currentThread();
    while (isLocked && lockedBy != currentThread) {
    wait();
    }
    isLocked = true;
    lockedBy = currentThread;
    lockCount++;
    }




    public synchronized void unlock() {
    if (Thread.currentThread() == lockedBy) {
    lockCount--;
    if (lockCount == 0) {
    isLocked = false;
    lockedBy = null;
    notify();
    }
    }
    }
    }

    在这个示例中,锁的状态由三个变量来维护:

    - isLocked:表示锁是否被占用,初始值为false。

    - lockedBy:表示当前占用锁的线程,初始值为null。

    - lockCount:表示当前线程获取锁的次数,初始值为0。

    在lock()方法中,首先获取当前线程,并且使用while循环判断锁是否被占用,以及是否是当前线程占用的。如果不满足条件,则当前线程进入等待状态。当锁被释放时,当前线程可以继续执行,并且获取锁。此时,将isLocked设置为true,lockedBy设置为当前线程,并且lockCount加1。

    在unlock()方法中,首先判断当前线程是否是锁的拥有者,只有拥有者才能释放锁。如果是拥有者,则将lockCount减1,如果lockCount为0,则表示锁已完全释放,将isLocked设置为false,lockedBy设置为null,并且通过notify()方法唤醒其他等待的线程。

    这样,就实现了一个简单的可重入锁。同一个线程可以多次获取锁,并且可以安全地释放锁,避免了死锁的发生。

    4、java中的可重入锁

    在Java中,可重入锁的实现有以下几种:

    1. ReentrantLock:这是Java提供的一个可重入锁实现类,它提供了与synchronized关键字类似的功能。ReentrantLock具有更高的灵活性和扩展性,可以实现公平锁和非公平锁,并且支持条件变量等高级功能。

    2. ReentrantReadWriteLock:这是Java提供的一个可重入的读写锁实现类,它将锁分为读锁和写锁,多个线程可以同时获取读锁,但只有一个线程可以获取写锁。读写锁适用于读多写少的场景,可以提高并发性能。

    3. StampedLock:这是Java 8新增的一个可重入的读写锁实现类,它比ReentrantReadWriteLock更加灵活和高效。StampedLock提供了乐观读锁和悲观读锁的支持,可以在不阻塞写操作的情况下进行读操作。

    4. synchronized关键字:虽然不是专门的可重入锁实现类,但synchronized关键字在Java中也可以实现可重入锁的功能。当一个线程已经获取了某个对象的锁时,再次获取该对象锁时不会被阻塞,而是会直接获取锁。

    这些可重入锁实现类都提供了类似的功能,但使用方式和性能特点有所不同。

    5、ReentrantLock的使用

    下面简单演示ReentrantLock的使用:

      import java.util.concurrent.locks.ReentrantLock;


      public class ReentrantExample {
      private static ReentrantLock lock = new ReentrantLock();


      public static void main(String[] args) {
      Thread thread1 = new Thread(() -> {
      lock.lock();
      try {
      System.out.println("Thread 1: First lock acquisition");
      lock.lock();
      try {
      System.out.println("Thread 1: Second lock acquisition");
      } finally {
      lock.unlock();
      System.out.println("Thread 1: Second lock released");
      }
      } finally {
      lock.unlock();
      System.out.println("Thread 1: First lock released");
      }
      });


      Thread thread2 = new Thread(() -> {
      lock.lock();
      try {
      System.out.println("Thread 2: First lock acquisition");
      } finally {
      lock.unlock();
      System.out.println("Thread 2: First lock released");
      }
      });


      thread1.start();
      thread2.start();
      }
      }

      在上述代码中,创建了一个可重入锁ReentrantLock。在thread1中,线程首先获取锁,然后再次请求同一个锁。由于可重入锁的特性,线程能够再次成功获取锁,并继续执行。最后,线程释放锁。在thread2中,线程获取锁后,执行相应的操作,然后释放锁。

      运行上述代码,可以看到输出结果如下:

      从输出结果可以看出,线程1首先获取锁,然后再次请求同一个锁,成功获取并继续执行。线程2在线程1释放锁之后获取锁,并继续执行。这证明了可重入锁的作用,即允许一个线程多次获取同一个锁而不会发生死锁。

      6、总结

      可重入锁是指同一个线程可以多次获取同一个锁,而不会发生死锁。

      1. 原理:可重入锁通过线程标识和锁计数器来实现。线程标识用于标识锁的拥有者,锁计数器用于记录当前线程对锁的获取次数。

      2. 实现类:Java中提供了几种可重入锁的实现类,包括ReentrantLock、ReentrantReadWriteLock和StampedLock。每个实现类都有自己的特点和适用场景。

      3. 可重入锁具有以下特性:

         - 同一个线程可以多次获取同一个锁。

         - 锁计数器记录了当前线程对锁的获取次数。

         - 只有当锁计数器为0时,锁才会被完全释放。

         - 其他线程在锁被完全释放之前无法获取该锁。

      4. 使用方式:可重入锁的使用方式与普通锁类似,通常使用lock()方法获取锁,使用unlock()方法释放锁。在同一个线程中可以嵌套使用锁,而不会导致死锁。

      5. 避免死锁:可重入锁的设计可以避免死锁的发生,因为同一个线程可以多次获取同一个锁。但仍需注意编程时的锁的获取和释放的顺序,以避免死锁的可能性。

      6. 性能:可重入锁相对于synchronized关键字在性能上可能更有优势,因为它提供了更高的灵活性和扩展性,并且支持一些高级功能,如条件变量、读写锁等。

      最后感谢大家的关注!

      -END-


      相关推荐:

      文章转载自1024笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论