JAVA中AQS锁原理解析

AQS(AbstractQueuedSynchronizer)是Java中用于实现锁和同步器的基础框架。本文将对AQS的原理进行详细解析,包括其内部数据结构、实现方式以及代码示例。

AQS简介

AQS是一个抽象类,提供了一种实现同步器的简单框架。它定义了两种类型的同步器:独占模式和共享模式。独占模式同步器在同一时刻只能有一个线程获取锁,而共享模式同步器可以允许多个线程同时获取锁。

内部数据结构

AQS内部维护了一个FIFO队列,用于存储等待获取锁的线程。这个队列采用了一个双向链表的数据结构,每个节点表示一个等待获取锁的线程。另外,AQS还维护了一个状态变量,用于标识锁的状态。

实现方式

AQS通过一个volatile类型的int变量来表示锁的状态。当该变量的值为0时,表示锁是未获取状态;当值大于0时,表示锁已被某个线程获取;当值小于0时,表示锁在等待状态。

独占模式

对于独占模式同步器,AQS主要通过实现acquire和release方法来实现加锁和释放锁的逻辑。

acquire方法:

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

release方法:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

在acquire方法中,首先尝试使用tryAcquire方法来获取锁。如果获取失败,则调用acquireQueued方法将当前线程加入到等待队列中,并且阻塞当前线程。当锁被释放后,会唤醒等待队列中的线程。

在release方法中,首先调用tryRelease方法来释放锁。如果释放成功,则唤醒等待队列中的下一个线程。

共享模式

对于共享模式同步器,AQS的实现方式与独占模式基本相同,只是对于等待队列的操作稍有不同。在共享模式中,等待队列中的线程可以同时获取锁。

acquireShared方法:

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null;
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

在acquireShared方法中,首先调用tryAcquireShared方法来尝试获取锁。如果获取失败,则调用doAcquireShared方法将当前线程加入到等待队列中,并且阻塞当前线程。当锁被释放后,会唤醒等待队列中的线程。

使用示例

下面通过一个简单的示例来说明AQS的使用方法。

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class MyLock extends AbstractQueuedSynchronizer {
    
    @Override
    protected boolean tryAcquire(int arg) {
        if (getState() == 0) {
            // 锁未被获取,尝试获取锁
            if (compareAndSetState(0, arg)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
        }
        return false;
    }
    
    @Override
    protected boolean tryRelease(int arg) {
        if (getState() == arg) {
            // 锁已被当前线程持有,释放锁
            setState(0);
            setExclusiveOwnerThread(null);
            return true;
        }
        return false;
    }
    
    public void lock() {
        acquire(1);
    }
    
    public void unlock() {
        release(1);
    }
}

在上述示例中,我们自定义了一个锁类MyLock,并继承了AbstractQueuedSynchronizer。通过重写tryAcquiretryRelease方法,实现了加锁和释放锁的逻辑。其中,lock方法调用acquire方法实现加锁,unlock方法调用release方法实现释放锁。

使用示例:

public class Main {
    private static MyLock lock = new MyLock();
    private static int count = 0;
    
    public static void main(String[] args) {
        Runnable task = () -> {
            lock.lock();
            try {
                for (int i = 0; i < 10000; i++) {
                    count++;
                }
            } finally {
                lock.unlock();
            }
        };
        
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        
        t1.start();
        t2.start();
        
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("count: " + count);
    }
}

总结

总结来说,AQS是Java中一种基于等待/通知机制实现的抽象锁,通过内部的状态变量和等待队列来管理线程的状态和同步操作。在实际应用中,AQS被广泛应用于各种锁的实现,如ReentrantLock和Semaphore等。通过深入理解AQS的原理和机制,我们可以更好地理解和使用Java中的锁机制。

posted @   yue_stack  阅读(45)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示