2024-03-23
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1334693028

回答

ReentrantLock 是一种可重入的排它锁,主要用来解决多线程对共享资源竞争的问题。ReentrantLock 提供了比 synchronized 更加灵活、更强大的锁机制。

它的核心特性有如下几个:

  1. 可重入性ReentrantLock 是一个可重入锁,也就是说同一个线程可以多次获取同一个锁。
  2. 公平锁和非公平锁ReentrantLock 支持公平和非公平两种方式。
    1. 公平锁意味着等待时间最长的线程将会首先获得锁
    2. 非公平锁可能会让新请求的线程优先于正在等待的线程获得锁。

ReentrantLock 的底层实现是基于 AbstractQueuedSynchronizer 框架,即 AQSAQS 使用一个 volatile 整数(state)来表示同步状态,并维护了一个等待线程的队列。在 ReentrantLock 中,当线程尝试获取锁时,如果锁未被占用(即 state 为 0),该线程将成功获取锁并将 state 值 + 1。如果锁已被其他线程占用,尝试获取锁的线程将被加入到 AQS 维护的等待队列中,并处于阻塞状态。当锁被释放时,AQS 会根据锁的公平性或非公平性策略选择下一个获取锁的线程,并将其从队列中移除和唤醒。由于其内部采用了无锁模式的 CAS(Compare-And-Swap)操作和 volatile 变量,ReentrantLock 能有效地减少锁的竞争,并提高并发性能。同时,ReentrantLock 的可重入特性允许线程在持有锁的情况下重复获取同一锁,从而在递归调用等情况下避免死锁。

详解

ReentrantLock 简介

ReentrantLock 实现 Lock 接口,是一种可重入的独占锁。相比于 synchronized 同步锁,ReentrantLock 更加灵活,拥有更加强大的功能。它拥有如下几个特性:

  • 可重入性ReentrantLock 是一个可重入锁,这意味着同一个线程可以多次获得同一个锁。如果某个线程已经持有锁,它可以再次请求并获得锁,而不会被阻塞。
  • 公平性选项ReentrantLock 支持公平和非公平两种方式。在创建 ReentrantLock 实例时,我们可以选择是创建一个公平锁还是非公平锁。对于公平锁,按照线程等待的顺序来分配锁;而非公平锁可能会优先分配给最近请求锁的线程。
  • 锁定与解锁的控制:与 synchronized 不同,ReentrantLock 提供了显式的锁定和解锁方法。通过 lock() 方法来获取锁,并通过 unlock() 方法来释放锁,相比 synchronized ,这种方式提供了更灵活的锁管理方式。
  • 支持超时ReentrantLock 提供了带有超时功能的锁获取方法,如 tryLock(),允许线程在指定时间内尝试获取锁,超时未获取则放弃,这有助于防止死锁。
  • 条件变量ReentrantLock 关联了 Condition 类的对象,可以让线程在某些条件下挂起和被唤醒。
  • 中断响应ReentrantLock 提供了能让线程在等待锁的过程中响应中断 的方法:lockInterruptibly() 。该方法允许线程在等待锁的过程中,如果被中断,则抛出一个 InterruptedException

下图是它的 UML 结构类图:

ReentrantLock 实现 Lock 接口,内部有一个 Sync 的引用,Sync 继承 AbstractQueuedSynchronizer ,是 ReentrantLock 实现锁机制的基石和根本所在。FairSync 和 NonFairSync 分别对应公平锁和非公平锁,他们都继承 Sync。

ReentrantLock 内部有一个非常重要的成员变量:

    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

这个变量为 ReentrantLock 提供了锁机制的实现,我们就可以认为它就是锁。这个变量用来指向 Sync 的子类,即 FairSync 和 NonFairSync,在构造 ReentrantLock 实例的时候就确定 sync 是 FairSync 还是 NonFairSync:

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

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

源码分析

公平锁

ReentrantLock 默认非公平锁,我们可以调用构造来构建公平锁:new ReentrantLock(true):

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

获取锁

调用 lock() 获取公平锁:

final void lock() {
  acquire(1);
}
        
public final void acquire(int arg) {
  if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();
}
  • tryAcquire(arg):尝试获取锁
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        // 获取同步状态 state
        int c = getState();
        // = 0 表示该锁还没被占用
        if (c == 0) {
            // hasQueuedPredecessors()判断同步队列中是否有线程在等待
            // compareAndSetState() 通过 CAS 的方式来获取锁
            if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                // 获取锁成功
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // c != 0 表示当前锁已有线程占用
        else if (current == getExclusiveOwnerThread()) {
            // 重入
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

流程图如下:

hasQueuedPredecessors() 是用来实现公平锁的机制:

    public final boolean hasQueuedPredecessors() {
        Node t = tail;   //尾节点
        Node h = head;   //头节点
        Node s;
        //头节点 != 尾节点
        //同步队列第一个节点不为null
        //当前线程是同步队列第一个节点
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

释放锁

调用 unlock() 方法释放锁:

    public void unlock() {
        sync.release(1);
    }
    
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            AbstractQueuedSynchronizer.Node h = head;
            if (h != null && h.waitStatus != 0)
                // 唤醒下一个节点
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

调用 tryRelease() 释放锁,由于 ReentrantLock 是可重入的,所以当前线程不一定已完全释放锁资源,如果只释放了部分,则返回 false,否则返回 true。返回 true,则需要唤醒下一个节点(线程)。

    protected final boolean tryRelease(int releases) {
        // state -1
        int c = getState() - releases;
        // 释放锁的线程和持有锁的线程要是同一个
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        // state = 0,说明锁资源已完全释放,返回 true,同时将锁的持有者置为空
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

流程图如下:

非公平锁

ReentrantLock 默认是非公平锁,我们有两种方式创建非公平锁 ReentrantLock

  1. new ReentrantLock()
  2. new ReentrantLock(false)

获取锁

        final void lock() {
            // 首先尝试获取锁
            if (compareAndSetState(0, 1))
                // 成功,将锁的持有者设置为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

非公平锁首先会进行一次快速获取锁,如果获取成功,就将锁的持有者设置为自己。如果这次快速获取锁失败,则调用 acquire() 参与锁竞争:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

这里和公平锁一样,调用 tryAcquire() 获取锁,如果失败则添加到 CLH 队列去。

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
    
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

nonfairTryAcquire() 与 公平锁的 tryAcquire() 几乎一模一样,就少了一个 hasQueuedPredecessors() 的判断,因为非公平锁不需要判断当前线程是否为首节点线程,谁抢到就是谁的,简单粗暴。

流程图如下:

非公平锁的释放机制和公平锁一样,大明哥这里就不做介绍了。

阅读全文