Java多线程(3)----浅谈队列同步器(AQS)

本文深入介绍了Java并发编程中的重要工具——AbstractQueuedSynchronizer(同步器)。同步器是一种基于模板方法设计模式的框架,用于构建锁和其他同步组件。它维护了一个同步状态和一个FIFO等待队列,通过模板方法和可重写方法管理线程的同步。在自定义同步组件时,通常创建一个继承自AbstractQueuedSynchronizer的静态内部类,并实现特定的同步逻辑。同步器的关键方法包括acquire和release系列,以及tryAcquire和tryRelease等,它们确保了同步状态的原子性和线程调度。此外,文章还探讨了同步器如何通过节点(Node)管理和唤醒等待在队列中的线程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1,同步器介绍

队列同步器(AbstractQueuedSynchronizer)以下简称同步器,它是用来构建锁或者其他同步组建的基础框架。其内部使用一个int成员变量用于表示同步状态(简单理解为锁的持有情况);以及一个内置的FIFO队列,每个试图获取锁的线程失败时,会被构建成一个节点(Node),并通过同步器提供的入队方法加入到队列中。这个节点的源码如下:

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

	......
    
    static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3;

        volatile int waitStatus;

        volatile Node prev;

        volatile Node next;

        volatile Thread thread;

        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }

        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }
    
    ......
    
}

2,同步器的设计

同步器是基于模板方法设计模式实现的,这种设计模式简单粗暴点的理解就是:一个类A中有若干个方法,有一类方法是类A在设计之初就已经实现好了,子类无法修改的了,称为模板方法;有些方法在类A中并没有实现,而是供子类根据业务逻辑进行代码实现,也就是可重写方法;以及一些其他方法。模板方法内部已经写好了对其他方法的调用逻辑

因此同步器内有两类需要我们关注的方法

  • 模板方法:此类方法不需要我们重写,因为其已经设计好了如何调用可重写方法
  • 可重写方法:这类方法需要我们按照需要的逻辑进行重写

除了上述两类方法外,同步器还写好了一些方法,当我们需要对同步状态进行更改的时候,就可以调用这些方法,如getState()setState(int newState)compareAndSetState(int expect,int update)

同步器是实现锁(也可以是任意同步组件)的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。可以这样理解二者之间的关系:锁是面向使用者的,它定义了 使用者与锁交互的接口(比如可以允许两个线程并行访问),隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待 与唤醒等底层操作。锁和同步器很好地隔离了使用者和实现者所需关注的领域。----《Java并发编程艺术》第5章

如何使用同步器来构建自定义的同步组件?

因为同步器是基于模板方法设计的,所以通常情况下,在自定义同步组件A中创建一个静态内部类继承自AbstractQueuedSynchronizer,并选择性的重写同步器类的可重写方法。这个静态内部类也就是自定义的同步器。同步组件A则调用同步器封装的方法即可。

例如书中有这么一个独占锁的同步器:

class Mutex implements Lock {
    // 静态内部类,自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        // 是否处于占用状态
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
        // 当状态为 0 的时候获取锁
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        // 释放锁,将状态设置为 0
        protected boolean tryRelease(int releases) {
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        // 返回一个 Condition,每个 condition 都包含了一个 condition 队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }
    // 仅需要将操作代理到 Sync 上即可
    private final Sync sync = new Sync();
    public void lock() {
        sync.acquire(1);
    }
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    public void unlock() {
        sync.release(1);
    }
    public Condition newCondition() {
        return sync.newCondition();
    }
    public boolean isLocked() {
        return sync.isHeldExclusively();
    }
    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException 
    {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

3,模板方法&可重写方法

同步器提供了用来访问或修改同步状态的方法:

  • getState():获取当前同步状态
  • setState(int newState):设置当前同步状态
  • compareAndSetState(int expect,int update):使用 CAS 设置当前状态,该方法能够 保证状态设置的原子性。

除此之外,还有两类方法:

3.1,模板方法

方法名称描述
public final void acquire(int arg)独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回;否则当前线程会被构造成Node加入同步队列中
public final void acquireInterruptibly(int arg)与acquire(int arg)相同,但该方法响应中断,当前线程为获取到同步状态而进入同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException并返回
public final boolean tryAcquireNanos(int arg, long nanosTimeout)在acquireInterruptibly(int arg)的基础上增加了超时限制
public final void acquireShared(int arg)共享式的获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取到同步状态
public final void acquireSharedInterruptibly(int arg)与acquireShared(int arg)相同,但该方法响应中断
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)在acquireSharedInterruptibly(int arg)的基础上增加超时限制
public final boolean release(int arg)独占式的释放同步状态,该方法会在释放同步状态后,将同步队列中第一个节点包含的线程唤醒
public final boolean releaseShared(int arg)共享式的释放同步状态
public final Collection getQueuedThreads()获取等待在同步队列上的县城集合

acquire(int arg)为例,其源码如下:

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

3.2,可重写方法

方法名称描述
protected boolean tryAcquire(int arg)独占式的获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态
protected boolean tryRelease(int arg)独占式释放同步状态
protected int tryAcquireShared(int arg)共享式的获取同步状态,返回大于等于0的值表示获取成功,反之
protected boolean tryReleaseShared(int arg)共享式释放同步状态
protected boolean isHeldExclusively()当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程所独占

相关的源码如下:

    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }

    protected boolean tryReleaseShared(int arg) {
        throw new UnsupportedOperationException();
    }

    protected boolean isHeldExclusively() {
        throw new UnsupportedOperationException();
    }

4,同步器的实现

同步器是如何管理那些获取同步状态失败的线程?----同步队列

在这里插入图片描述

节点(Node)是构成同步队列的基础,同步器拥有首节点(head)和尾节点(tail),没有成功获取同步状态的线程将会成为节点加入该队列尾部。若有多个线程在同一个时间段中获取同步状态失败,那么将线程添加到队列尾部这个操作就有可能产生并发问题。所以同步器提供了一个基于CAS的设置尾节点的方法:compareAndSetTail(Node expect,Node update),它需要传递当前线程”认为“的尾节点和当前节点,设置成功之后,当前节点才会与之前的尾节点建立关联。

而当head指向的节点成功释放同步状态后,会访问next指向的下一个节点,将其唤醒。而被唤醒的节点会去获取同步状态,并且在获取成功时将自己设置为首节点(也就是head指向的节点)。因为首节点的指定是由”上一任“首节点指定的,所以并不存在并发问题。只需要正常的双向链表的节点变换即可。

未完待续……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值