aqs的公平非公平锁如何实现

AQSAbstractQueuedSynchronizer)是 Java 并发包(java.util.concurrent)中的一个重要基础类,它用来实现同步器的核心机制,比如 ReentrantLockSemaphoreCountDownLatch 等。AQS 提供了一个先进的队列(FIFO 队列)来管理竞争线程的排队和唤醒,并通过状态(state)来表示锁的持有状态。

AQS 中,公平锁非公平锁是两种不同的锁实现方式:

  • 公平锁:按照线程请求锁的顺序来分配锁,先请求的线程先获取锁,避免“饥饿”现象。

  • 非公平锁:允许“插队”,新来的线程可以直接尝试抢占锁,性能通常比公平锁好,但可能会导致某些线程长期得不到锁。

1. 公平锁和非公平锁的区别

  • 公平锁:线程获取锁的顺序严格按照它们请求的顺序,FIFO(先入先出)队列中最早等待的线程优先获得锁。
  • 非公平锁:线程尝试直接获取锁,而不管队列中是否有其他线程在等待。如果锁空闲,当前线程可以立即获取锁而不需要排队。

2. AQS 公平锁和非公平锁的实现

AQS 中,锁的公平性主要体现在锁的获取逻辑上。具体来说,AQS 提供了两个核心的锁实现类:

  • 公平锁ReentrantLock 的公平实现通过 FairSync 类来实现。
  • 非公平锁ReentrantLock 的非公平实现通过 NonFairSync 类来实现。

ReentrantLock 通过 AQS 提供的 acquiretryAcquire 方法实现锁的获取逻辑,而公平锁和非公平锁的核心区别就在于 tryAcquire 方法的实现。

2.1 非公平锁的实现

非公平锁允许线程直接尝试获取锁,而不考虑队列中是否有其他等待的线程。这种机制可以减少线程切换的开销,提升性能。

关键点:
  • 抢占式获取锁:非公平锁允许新来的线程直接尝试获取锁,而不管队列中是否有其他线程在排队。
  • 性能更高:由于直接进行抢占,线程切换次数较少,性能通常比公平锁更好。
非公平锁的 tryAcquire() 实现:
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    // 非公平的锁获取逻辑
    @Override
    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;
    }
}
  • NonfairSync 是非公平锁的具体实现类。
  • 当锁的状态为 0 时,表示锁是空闲的,当前线程将直接尝试通过 compareAndSetState(0, acquires) 获取锁,而不检查队列中是否有其他线程在等待。
  • 如果当前线程已经持有锁,则可以重入锁,并通过增加 state 的值来表示重入次数。
2.2 公平锁的实现

公平锁的实现严格按照线程请求锁的顺序来分配锁。即使当前线程能够立即获取锁,它也必须先检查队列中是否有其他等待的线程,只有在队列为空或者当前线程是队列中的第一个时,才能获取锁。

关键点:
  • FIFO 顺序:公平锁会检查队列中是否有其他线程在等待,只有队列头的线程才能获取锁。
  • 避免插队:公平锁保证了“先到先得”的顺序,防止某些线程长时间得不到锁。
公平锁的 tryAcquire() 实现:
static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    // 公平的锁获取逻辑
    @Override
    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) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}
  • FairSync 是公平锁的具体实现类。
  • 当锁是空闲的(c == 0),公平锁首先通过 hasQueuedPredecessors() 方法检查队列中是否有其他等待的线程。如果队列为空,或者当前线程是等待队列中的第一个线程,才允许当前线程获取锁。
  • 如果当前线程已经持有锁,则可以重入锁,并通过增加 state 的值来表示重入次数。
2.3 hasQueuedPredecessors() 方法

在公平锁的实现中,hasQueuedPredecessors() 是判断当前线程是否可以获取锁的关键方法。

public final boolean hasQueuedPredecessors() {
    // 返回 true 表示当前线程前面有等待的线程
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
}
  • 该方法返回 true,表示当前线程前面有其他线程在等待队列中,因此当前线程不能获取锁。
  • 该方法的作用是检查等待队列中的第一个节点是否是当前线程,如果不是,说明当前线程前面还有其他线程在等待。

3. 公平锁与非公平锁的性能差异

  • 非公平锁性能更高:由于非公平锁允许新线程直接尝试获取锁,减少了上下文切换的开销,因此在高竞争环境下,非公平锁的性能通常优于公平锁。
  • 公平锁避免线程饥饿:公平锁保证了“先到先得”的顺序,适合那些需要严格控制线程执行顺序的场景,避免了某些线程长期得不到锁的情况。
  • 选择何种锁:大多数情况下,非公平锁的性能更好,因为它减少了线程切换的开销。但如果某些线程可能会因为其他线程频繁“插队”而长期得不到锁,则应该选择公平锁。

4. 实际应用中的选择

  • ReentrantLock:Java 提供的 ReentrantLock 类支持两种模式:公平模式和非公平模式。默认情况下,ReentrantLock 是非公平锁,但可以通过构造函数控制锁的公平性。
构造公平锁与非公平锁的例子:
// 创建一个非公平的 ReentrantLock(默认)
ReentrantLock nonFairLock = new ReentrantLock();

// 创建一个公平的 ReentrantLock
ReentrantLock fairLock = new ReentrantLock(true);
  • 默认非公平ReentrantLock 默认是非公平锁,以提高性能。
  • 公平锁通过构造参数控制:当你需要公平锁时,可以在构造 ReentrantLock 时传入 true 参数。

5. 总结

  • 公平锁通过严格的排队机制,保证了线程按照请求顺序获取锁,避免了线程饥饿现象;但由于需要频繁检查等待队列,可能会导致性能下降。
  • 非公平锁允许线程直接尝试获取锁,不必排队,这种机制减少了线程切换的开销,因此性能通常优于公平锁,但可能导致某些线程长时间得不到锁。

在实际开发中,非公平锁通常是首选,除非有明确的需求(如希望严格控制执行顺序或避免线程饥饿现象),才使用公平锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值