Java AQS 公平与非公平锁的原理

引言

在多线程编程中,锁是一种重要的机制,用于控制多个线程对共享资源的访问。在 Java 中,AbstractQueuedSynchronizer(AQS)是一种用于构建锁和同步器的基础框架。它支持公平和非公平锁。本文将详细讲解 AQS 的工作原理,并展示如何实现公平和非公平锁。

流程概述

以下是实现公平/非公平锁的主要流程:

步骤 描述
步骤1 创建一个锁的类,继承 AQS
步骤2 重写 tryAcquire 方法以支持获取锁
步骤3 重写 tryRelease 方法以支持释放锁
步骤4 实现公平锁机制(可选)
步骤5 实现非公平锁机制

步骤详解

步骤1:创建锁的类

首先,我们创建一个自定义锁类,并继承 AbstractQueuedSynchronizer

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyLock extends AbstractQueuedSynchronizer {
    // 判断锁是否被占用
    @Override
    protected boolean isHeldExclusively() {
        return getState() == 1;  // 当状态为1时表示锁被占用
    }
}

步骤2:重写 tryAcquire

tryAcquire 方法尝试获取锁,返回是否成功。

@Override
protected boolean tryAcquire(int arg) {
    if (compareAndSetState(0, 1)) { // 尝试将状态从0变为1
        setExclusiveOwnerThread(Thread.currentThread()); // 设置当前线程为持有锁的线程
        return true; // 获取锁成功
    }
    return false; // 获取锁失败
}

步骤3:重写 tryRelease

tryRelease 方法尝试释放锁。

@Override
protected boolean tryRelease(int arg) {
    if (getExclusiveOwnerThread() != Thread.currentThread()) {
        throw new IllegalMonitorStateException(); // 只有持有锁的线程才能释放锁
    }
    setExclusiveOwnerThread(null); // 清除持有锁的线程
    setState(0); // 将状态重置为0
    return true; // 释放锁成功
}

步骤4:实现公平锁机制

公平锁确保线程按照请求的顺序获取锁。

@Override
protected boolean tryAcquire(int arg) {
    Thread currentThread = Thread.currentThread();
    if (hasQueuedPredecessors()) { // 检查队列中是否有前驱线程
        return false; // 如果有,则不获取锁
    }
    return super.tryAcquire(arg); // 否则,调用父类方法尝试获取锁
}

步骤5:实现非公平锁机制

非公平锁允许后续请求的线程可能直接获取锁。

@Override
protected boolean tryAcquire(int arg) {
    // 直接尝试获取锁,不管前面是否有其他线程在排队
    if (getState() == 0 && compareAndSetState(0, 1)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true; // 获取锁成功
    }
    return false; // 获取锁失败
}

代码结构图

以下是类结构示意图(简化):

classDiagram
    class MyLock {
        -boolean isHeldExclusively()
        -boolean tryAcquire(int arg)
        -boolean tryRelease(int arg)
    }

交互序列图

在多线程获取锁的情景中,公平与非公平锁的区别如下:

sequenceDiagram
    participant T1 as Thread 1
    participant T2 as Thread 2
    participant Lock as MyLock

    T1->>Lock: 请求锁
    Lock-->>T1: 获取锁成功
    T2->>Lock: 请求锁
    Lock-->>T2: 等待
    T1->>Lock: 释放锁
    Lock-->>T2: 获取锁成功 (公平锁)

时间线图

以下是公平和非公平锁的请求处理甘特图示例:

gantt
    title 公平与非公平锁的请求处理
    dateFormat  HH:mm
    section 公平锁
    T1获取锁      :a1, 00:00, 1m
    T2等待        :a2, 00:01, 1m
    T1释放锁      :after a1  , 1m
    T2获取锁      :after a2  , 1m

    section 非公平锁
    T1获取锁      :b1, 00:00, 1m
    T2获取锁      :b2, 00:00, 1m

结论

通过以上步骤,我们实现了公平与非公平锁。公平锁确保线程按照顺序获取锁,而非公平锁则允许等待的线程在某些情况下先获取锁。AQS 提供了一个强大的框架,使得我们可以在此基础上构建更复杂的同步器。希望这篇文章能帮助你理解 AQS 的实现原理和使用方式。