ReentrantLock的底层原理以及优缺点

后端 潘老师 6个月前 (11-12) 125 ℃ (0) 扫码查看

本文重点讲解ReentrantLock的底层原理以及优缺点内容,为了深入了解Reentrant Lock的底层原理,我们需要从几个关键方面入手:锁的状态管理、可重入性、公平性与非公平性、以及条件变量的使用。接下来,我将详细解释这些方面。

1. 锁的状态管理

Reentrant Lock使用一个状态变量来表示锁的持有情况。这个状态通常用来表示锁被重入的次数。

// AbstractQueuedSynchronizer类中的部分代码
abstract static class Sync extends AbstractQueuedSynchronizer {
    // ...
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }
}

在上面的代码片段中,AbstractQueuedSynchronizer(AQS)是Reentrant Lock实现的基础。它使用getExclusiveOwnerThread()方法来检查当前线程是否是锁的持有者。

拓展(AbstractQueuedSynchronizer)

AbstractQueuedSynchronizer(简称AQS)是Java并发包中的一个核心类,它提供了一种有效的框架来实现依赖于先进先出(FIFO)等待队列的阻塞锁和相关的同步器(如信号量、事件等)。它是许多同步类的基础,包括ReentrantLockSemaphoreCountDownLatchFutureTask

原理和结构

状态(State): AQS使用一个整型的变量来表示同步状态。这个状态是同步器的核心,用于控制同步器是否被占用。

节点(Node)和等待队列: 当线程尝试获取同步状态失败时,AQS会将该线程包装成一个节点(Node)并将其加入到队列中。这个队列是一个FIFO队列,用于管理等待获取同步状态的线程。

独占模式和共享模式: AQS支持两种同步模式。独占模式在任何时候只允许一个线程持有同步状态,而共享模式允许多个线程同时持有同步状态。

核心方法

获取和释放方法:

acquire(int arg)release(int arg)用于实现独占式的获取和释放同步状态。

acquireShared(int arg)releaseShared(int arg)用于实现共享式的获取和释放同步状态。

同步状态管理方法:

getState(), setState(int newState), 和compareAndSetState(int expect, int update)用于管理同步状态。

等待队列管理方法:

hasQueuedThreads()hasContended()用来检查是否有线程在等待队列中。

使用AQS的优点

降低复杂性: AQS抽象了一系列复杂的同步控制逻辑,使得开发者可以通过实现几个方法来快速创建可靠的同步器。

高效和可靠: AQS提供的框架经过了良好的测试和优化,能够有效地管理线程间的同步。

灵活性: 支持独占和共享两种模式,可以满足不同场景的同步需求。

AQS的缺点

复杂性: 尽管AQS降低了实现同步器的复杂度,但理解其内部工作原理仍然需要一定的并发编程知识。

直接使用局限性: AQS是一个用于构建锁和同步器的框架,它本身在大多数情况下不直接用于编程。

结论

AbstractQueuedSynchronizer是Java并发编程中的一个重要组件。它通过提供一组方法来管理同步状态、实现锁的获取与释放,并管理等待获取锁的线程队列,从而为开发高效且可靠的同步器提供了坚实的基础。尽管它的直接使用相对复杂,但它背后的概念和机制对理解Java并发编程至关重要。

2. 可重入性

Reentrant Lock的可重入性是指同一个线程可以多次获取同一个锁。这是通过在锁内部记录锁的所有者以及锁的重入次数来实现的。

public void lock() {
    sync.lock();
}

protected final boolean tryAcquire(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;
}

在这段代码中,tryAcquire方法首先检查锁是否已经被占用(即状态c不为0)。如果没有被占用,它尝试设置当前线程为锁的所有者。如果已经被当前线程占用,它简单地增加重入计数。

3. 公平性与非公平性

Reentrant Lock提供了公平和非公平两种模式。在公平模式下,等待最久的线程会优先获取锁。而在非公平模式下,这种排序不被保证。

static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }

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

在这里,FairSync类扩展了Sync,并重写了tryAcquire方法以添加检查是否有等待更久的线程。

4. 条件变量

Reentrant Lock使用Condition对象来支持线程间的协作,这允许在特定条件下挂起和通知线程。

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

    public Condition newCondition() {
        return sync.newCondition();
    }

    abstract static class Sync extends AbstractQueuedSynchronizer {
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
    }
}

在上述代码中,newCondition()方法创建一个新的ConditionObject实例,它是AbstractQueuedSynchronizer.ConditionObject的一个实例,提供了等待/通知的功能。

当讨论Reentrant Lock(可重入锁)的优势和劣势时,我们需要从多个角度来考虑,包括它在性能、灵活性、易用性以及其它方面的表现。

5.ReentrantLock优点

灵活性与功能性

  • 可中断的锁获取: Reentrant Lock允许在等待锁的过程中响应中断,这是synchronized无法提供的功能。
  • 尝试锁定与超时: 提供了tryLock()方法,可以尝试获取锁而不是无限期等待,还可以设置超时时间。
  • 公平锁选项: 可以选择创建一个公平锁,确保按照请求的顺序获得锁,而synchronized总是非公平的。
  • 支持多个条件变量: Reentrant Lock可以与多个Condition实例一起使用,允许更复杂的线程间协调。

性能

在高并发环境中,特别是锁竞争不是特别激烈的情况下,Reentrant Lock通常提供比synchronized更好的性能。

可重入性

允许同一个线程多次获得同一把锁,减少了死锁的风险。

6.ReentrantLock缺点

复杂性

相对于synchronized,Reentrant Lock的使用更复杂,需要手动管理锁的获取和释放,增加了编程的复杂性。

风险

  • 错误的使用(如忘记释放锁)可能导致严重问题,例如死锁或资源泄漏。
  • 由于Reentrant Lock不是自动管理的,因此在异常处理中必须非常小心,确保在finally块中释放锁。

资源消耗

相比synchronized,Reentrant Lock可能在某些情况下更加消耗资源,特别是在使用公平锁的情况下。

锁的升级和降级不支持

Reentrant Lock不支持锁的升级(从读锁升级为写锁)和降级(从写锁降级为读锁),这在某些场景下可能是一个限制。

7.总结

Reentrant Lock在灵活性、功能性和性能方面提供了显著的优势,特别适合于需要高级同步特性的复杂应用场景。然而,这些优势也伴随着更高的复杂性和潜在的风险。

因此,选择Reentrant Lock还是synchronized需要根据具体的应用场景和需求来决定。简单场景下,使用synchronized可能更为合适,因为它更简单且在JVM层面得到了优化。而在复杂的多线程应用中,Reentrant Lock的高级功能可能更有价值。

以上就是ReentrantLock的底层原理以及优缺点的全部内容,你学会了吗?


版权声明:本站文章,如无说明,均为本站原创,转载请注明文章来源。如有侵权,请联系博主删除。
本文链接:https://www.panziye.com/back/11111.html
喜欢 (0)
请潘老师喝杯Coffee吧!】
分享 (0)
用户头像
发表我的评论
取消评论
表情 贴图 签到 代码

Hi,您需要填写昵称和邮箱!

  • 昵称【必填】
  • 邮箱【必填】
  • 网址【可选】