J.U.C-AQS框架同步组件之可重入锁ReentrantLock详解

ReentrantLock?

synchronized是托管给JVM执行的,Lock的锁定是通过代码实现的。所以Lock比较灵活,可以便于开发人员根据合适的场景进行操作,Lock是一个接口,需要实现它来进行使用,ReetrantLock是Lock的主要实现类,ReetrantLock是一个可重入锁,同时可以指定公平锁非公平锁,我们来具体看一下他的实现方式。

什么是可重入锁?

为什么叫重入锁呢?
ReentrantLock,我们把它拆开来看就明了了。

Re-Entrant-Lock:即表示可重新反复进入的锁,但仅限于当前线程;

public void m() {
    lock.lock();
    lock.lock();
    try {
      // ... method body
    } finally {
      lock.unlock()
      lock.unlock()
    }
}

如示例代码所示,当前线程可以反复加锁,但也需要释放同样加锁次数的锁,即重入了多少次,就要释放多少次,不然也会导入锁不被释放。

试想一下,如果不设计成可重入锁,那自己如果反复给自己加锁,不是会把自己加死锁了吗?所以,到现在,重入锁的概念大概应该清楚了吧?

重入锁最重要的几个方法

这几个方法都是 Lock 接口中定义的:
在这里插入图片描述

1. lock()

获取锁,有以下三种情况:

  • 锁空闲:直接获取锁并返回,同时设置锁持有者数量为:1;
  • 当前线程持有锁:直接获取锁并返回,同时锁持有者数量递增1;
  • 其他线程持有锁:当前线程会休眠等待,直至获取锁为止;
2. lockInterruptibly()

获取锁,逻辑和 lock() 方法一样,但这个方法在获取锁过程中能响应中断。

3. tryLock()

从关键字字面理解,这是在尝试获取锁,获取成功返回:true,获取失败返回:false, 这个方法不会等待,有以下三种情况:

  • 锁空闲:直接获取锁并返回:true,同时设置锁持有者数量为:1;
  • 当前线程持有锁:直接获取锁并返回:true,同时锁持有者数量递增1;
  • 其他线程持有锁:获取锁失败,返回:false;
4)tryLock(long timeout, TimeUnit unit)

逻辑和 tryLock() 差不多,只是这个方法是带时间的。

5)unlock()

释放锁,每次锁持有者数量递减 1,直到 0 为止。所以也就是为什么 lock 多少次,就要对应 unlock 多少次。

6)newCondition

返回一个这个锁的 Condition 实例,可以实现 synchronized 关键字类似 wait/ notify 实现多线程通信的功能,不过这个比 wait/ notify 要更灵活,更强大!

代码示例:

最简单的加锁
private static void add() {
        reentrantLock.lock();
        try {
            count++;
        } finally {
            reentrantLock.unlock();
        }
    }

加锁和释放锁都在方法里面进行,可以自由控制,比 synchronized 更灵活,更方便。但要注意的是,释放锁操作必须在 finally 里面,不然如果出现异常导致锁不能被正常释放,进而会卡死后续所有访问该锁的线程。

ReentrantLockCondition组件搭配使用:
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class LockExample6 {

    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();

        new Thread(() -> {
            try {
                reentrantLock.lock();   // 此时被加入到了AQS等待序列中
                log.info("wait signal"); // 1
                condition.await();  // 又被AQS移除了(await操作释放了锁) 进入了Condition的等待队列中 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("get signal"); // 4 获得信号
            reentrantLock.unlock();
        }).start();

        new Thread(() -> {
            reentrantLock.lock();  // 获得锁 进入AQS等待序列中
            log.info("get lock"); // 2
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            condition.signalAll(); // 发送信号唤醒所有等待的线程 将Condition等待队列中的线程1节点的 取出并加入到AQS等待队列中
            log.info("send signal ~ "); // 3
            reentrantLock.unlock();
        }).start();
    }
}
发布了251 篇原创文章 · 获赞 424 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/qq_33709508/article/details/105468639