Java并发编程之AbstractQueuedSychronizer(抽象队列同步器,简称AQS)

简介: Java并发编程之AbstractQueuedSychronizer(抽象队列同步器,简称AQS)

AbstractQueuedSychronizer(抽象队列同步器,简称AQS)



1.JDK的并发包(包名:java.util.concurrent,以下简称JUC)下面提供了很多并发操作的工具类,如:ReentrantLock,CountDownLatch等。这些并发操作工具类的基础是AbstractQueuedSychronizer


2.*AQS内部维护了一个共享资源和两个队列:*一个是同步队列;一个是条件队列。


public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
    //同步队列的头结点
    private transient volatile Node head;
    //同步队列的尾结点
    private transient volatile Node tail;
    //同步队列的共享资源,队列的同步状态
    private volatile int state;
}


3.Node类的主要信息


static final class Node {
    //静态变量,标识节点以共享模式等待资源
    static final Node SHARED = new Node();
    //标识节点以独占模式等待资源
    static final Node EXCLUSIVE = null;
    //等待状态,节点取消等待资源
    static final int CANCELLED =  1;
    //等待状态,标识后继节点需要唤醒
    static final int SIGNAL    = -1;
    //等待状态,标识线程处于条件等待状态
    static final int CONDITION = -2;
    //等待状态,标识线程以共享模式获取资源,释放锁的行为将传播到后续节点,该状态作用于头节点
    static final int PROPAGATE = -3;
    //节点等待状态,是上述4中状态之一,或者为0
    volatile int waitStatus;
    //节点的前驱节点
    volatile Node prev;
    //节点的后继节点
    volatile Node next;
    //节点对应的线程
    volatile Thread thread;
    //条件等待时标识下一个等待条件的节点,指向条件队列中的下一个节点
    //或者,标识共享模式
    Node nextWaiter;
}


4.同步队列是用双向链表实现的,主要用于记录等待获取共享资源的线程


5.条件队列是一个单向链表的结构,链表中的元素也是Node,只不过条件队列中的元素使用Node的nextWaiter指向下一个元素。


6.AQS对外提供的protected类型的方法入手分析一下AQS的工作原理:


/**
    *尝试以独占模式获取共享资源
    *@param arg 表示需要获取资源的个数
    *@return true表示获取成功,false获取失败
    **/
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}
/**
    *尝试以独占模式释放共享资源
    *@param arg 表示释放资源的个数
    *@return true表示释放成功,false释放失败
    **/
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}
/**
    *尝试以共享模式获取共享资源
    *@param arg 表示获取资源的个数
    *@return true表示获取成功,false获取失败
    **/
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}
/**
    *尝试以共享模式释放共享资源
    *@param arg 表示释放资源的个数
    *@return true表示释放成功,false释放失败
    **/
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}
/**
    * 当前线程是否以独占模式获取了共享资源,该方法是在条件对象ConditionObject内部使用的,如果不需要条件等待,则无需实现该方法
    *@return true表示是,false表示否
    **/
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}


AQS以模板方法的模式,提供了多个线程对共享资源(state)操作的算法框架,上面的五个protected类型的方法主要用于尝试获取和释放共享资源,并不会阻塞当前线程,是AQS留给具体的业务操作类(如:ReentrantLock)来实现的。


7.AQS获取资源,释放资源等方法的具体代码:


/**
    *以独占模式获取共享资源,获取成功则返回,否则阻塞当前线程,并将当前线程放入同步队列等待获取资源
    *@param arg 表示需要获取资源的个数
    **/
public final void acquire(int arg) {
    //首先调用子类实现的tryAcquire方法,如果该方法返回true则表示获取成功,不进行后续判断
    //否则,调用acquireQueued方法将当前线程放入同步队列排队等待获取资源
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
/**
    *将当前线程包装成Node,并放入同步队列
    *@param mode 模式,共享模式或者独占模式
    *@return 当前线程所在的节点
    **/
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // 先尝试将节点放入队列的尾部,如果成功则返回,否则将节点入队
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //将当前节点放入同步队列,cas操作设置头和尾节点
    enq(node);
    return node;
}
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
/**
    *在队列中不断尝试获取资源
    *@param node 当前线程所在节点
    *@param arg 获取资源的个数
    *@return 等待资源过程中线程是否被中断
    **/
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获取当前节点的前驱节点
            final Node p = node.predecessor();
            //只有前驱是头节点的情况下才尝试获取锁,因为头结点是当前持有资源的线程所在的节点,如果前驱不是头节点那么没有必要尝试获取
            if (p == head && tryAcquire(arg)) {
                //获取成功后将当前节点设置为头结点
                setHead(node);
                //释放原来的头节点
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //如果节点前驱不是头结点或者获取资源失败则阻塞当前线程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
/**
    *以独占模式释放共享资源
    *@param arg 表示释放资源的个数
    *@return true表示释放成功,false释放失败
    **/
public final boolean release(int arg) {
    //尝试释放资源,如果失败则直接返回
    if (tryRelease(arg)) {
        //释放成功后,唤醒后继节点
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}


AQS中独占模式获取和释放资源的方法,这两个方法可以用于实现锁的功能,事实上ReentrantLock就是基于以上方法实现的。以共享模式获取和释放资源的方法,与独占模式类似


8.条件对象的等待和唤醒方法:


public class ConditionObject implements Condition, java.io.Serializable {    
  //第一个条件等待的节点
    private transient Node firstWaiter;
    //最后一个条件等待的节点
    private transient Node lastWaiter;
    /**
      * 唤醒条件队列中的第一个等待的线程,此时该线程将进入同步队列重新等待获取资源
      */
    public final void signal() {
        //判断当前线程是否以独占模式占有资源
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
            //将条件队列中的第一个线程,重新放入同步队列
            doSignal(first);
    }
    private void doSignal(Node first) {
        do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) &&
                 (first = firstWaiter) != null);
    }
    final boolean transferForSignal(Node node) {
        //如果CAS操作失败,说明线程取消获取共享资源,此时返回false,doSignal会尝试将下一个节点放入同步队列
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //将节点放入同步队列
        Node p = enq(node);
        int ws = p.waitStatus;
        //设置节点的前驱节点的状态为Node.SIGNAL
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
    //使获取共享资源的线程等待并进入条件队列,如果当前线程被中断则退出
    public final void await() throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //在条件队列中添加一个节点
        Node node = addConditionWaiter();
        //释放当前线程获取的共享资源
        int savedState = fullyRelease(node);
        int interruptMode = 0;
        //判断当前节点是否在同步队列中,如果不在则暂停当前线程
        while (!isOnSyncQueue(node)) {
            //暂停当前线程,该方法响应中断;当调用signal()方法的线程释放共享资源时,会从该处继续执行
            LockSupport.park(this);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        //重新等待获取锁
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null) // clean up if cancelled
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }
  }


9.总结:当线程获取共享资源成功时返回,否则进入同步队列等待前驱节点唤醒,此时当前线程处于阻塞状态(LockSupport.park方法使线程阻塞);前驱节点释放共享资源后会唤醒(LockSupport.unpark方法唤醒线程)后继节点,需要说明的是获取共享资源成功的线程必定是头节点所在的线程。当获取共享资源的线程,调用Condition.await()方法时,当前线程会进入条件队列等待;当其他线程调用Condition.signal()方法,并释放共享资源时当前线程会重新进入同步队列等待获取共享资源。


目录
相关文章
|
19天前
|
Java 编译器 开发者
深入理解Java内存模型(JMM)及其对并发编程的影响
【9月更文挑战第37天】在Java的世界里,内存模型是隐藏在代码背后的守护者,它默默地协调着多线程环境下的数据一致性和可见性问题。本文将揭开Java内存模型的神秘面纱,带领读者探索其对并发编程实践的深远影响。通过深入浅出的方式,我们将了解内存模型的基本概念、工作原理以及如何在实际开发中正确应用这些知识,确保程序的正确性和高效性。
|
1月前
|
Java 开发者
深入探索Java中的并发编程
本文将带你领略Java并发编程的奥秘,揭示其背后的原理与实践。通过深入浅出的解释和实例,我们将探讨Java内存模型、线程间通信以及常见并发工具的使用方法。无论是初学者还是有一定经验的开发者,都能从中获得启发和实用的技巧。让我们一起开启这场并发编程的奇妙之旅吧!
22 5
|
1月前
|
存储 Java
JAVA并发编程AQS原理剖析
很多小朋友面试时候,面试官考察并发编程部分,都会被问:说一下AQS原理。面对并发编程基础和面试经验,专栏采用通俗简洁无废话无八股文方式,已陆续梳理分享了《一文看懂全部锁机制》、《JUC包之CAS原理》、《volatile核心原理》、《synchronized全能王的原理》,希望可以帮到大家巩固相关核心技术原理。今天我们聊聊AQS....
|
1月前
|
算法 安全 Java
Java中的并发编程是如何实现的?
Java中的并发编程是通过多线程机制实现的。Java提供了多种工具和框架来支持并发编程。
17 1
|
1月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
1月前
|
安全 Java 测试技术
掌握Java的并发编程:解锁高效代码的秘密
在Java的世界里,并发编程就像是一场精妙的舞蹈,需要精准的步伐和和谐的节奏。本文将带你走进Java并发的世界,从基础概念到高级技巧,一步步揭示如何编写高效、稳定的并发代码。让我们一起探索线程池的奥秘、同步机制的智慧,以及避免常见陷阱的策略。
|
2月前
|
C# 开发者 数据处理
WPF开发者必备秘籍:深度解析数据网格最佳实践,轻松玩转数据展示与编辑大揭秘!
【8月更文挑战第31天】数据网格控件是WPF应用程序中展示和编辑数据的关键组件,提供排序、筛选等功能,显著提升用户体验。本文探讨WPF中数据网格的最佳实践,通过DevExpress DataGrid示例介绍其集成方法,包括添加引用、定义数据模型及XAML配置。通过遵循数据绑定、性能优化、自定义列等最佳实践,可大幅提升数据处理效率和用户体验。
57 0
|
4月前
|
Java C++
关于《Java并发编程之线程池十八问》的补充内容
【6月更文挑战第6天】关于《Java并发编程之线程池十八问》的补充内容
48 5
|
3月前
|
安全 Java 开发者
Java中的并发编程:深入理解线程池
在Java的并发编程中,线程池是管理资源和任务执行的核心。本文将揭示线程池的内部机制,探讨如何高效利用这一工具来优化程序的性能与响应速度。通过具体案例分析,我们将学习如何根据不同的应用场景选择合适的线程池类型及其参数配置,以及如何避免常见的并发陷阱。
52 1
|
3月前
|
监控 Java
Java并发编程:深入理解线程池
在Java并发编程领域,线程池是提升应用性能和资源管理效率的关键工具。本文将深入探讨线程池的工作原理、核心参数配置以及使用场景,通过具体案例展示如何有效利用线程池优化多线程应用的性能。