Java多线程之AbstractQueuedSynchronizer

本文深入解析了AbstractQueuedSynchronizer(AQS),一种用于构建Java并发工具的核心组件。AQS通过内部CLH队列管理锁资源,支持线程的排队、等待与唤醒,简化了并发工具的设计与实现。

简单介绍
AbstractQueuedSynchronizer这个词拆分解释就是抽象队列同步器的意思,一般简称AQS。Java许多并发工具类的内部实现都依赖于它,最常见的比如:ReentrantLock、CountDownLatch、Semaphore。

AQS的主要使用方式是继承它作为一个内部辅助类实现同步原语,像上面提到的几个并发工具类,其都包含了一个内部类Sync继承AQS来实现具体获取锁和释放锁的逻辑。它可以简化你的并发工具的内部实现,屏蔽同步状态管理、线程的排队、等待与唤醒等底层操作。

实现思路:
AQS内部维护一个CLH队列来管理锁。
线程会首先尝试获取锁,如果失败,则将当前线程以及等待状态等信息包成一个Node节点加到同步队列里。
接着会不断循环尝试获取锁(条件是当前节点为head的直接后继才会尝试),如果失败则会阻塞自己,直至被唤醒;
而当持有锁的线程释放锁时,会唤醒队列中的后继线程。

AQS结构:
可以看下AbstractQueuedSynchronizer的主要属性、内部类以及原子类方法:

	// 链表头结点
    private transient volatile Node head;
    
    // 链表尾节点
    private transient volatile Node tail;
    
    // 线程获取锁的状态,默认为0代表还没有线程获取到锁,1代表有一个线程获取到了一次锁,大于1代表线程获取到了多次锁(重入锁的实现)
    private volatile int state;
    
    static final class Node {
		static final Node SHARED = new Node();
	    static final Node EXCLUSIVE = null;
	    
    	// 表示当前的线程被取消
	    // 当前线程所在节点的前继节点内线程状态为此值时,当前节点内线程可值机尝试获取锁而不用阻塞
	    static final int CANCELLED =  1;
	    
	    // 表示当前节点的后继节点包含的线程需要运行,也就是unpark
	    static final int SIGNAL    = -1;
	    
	    // 表示当前节点在等待condition,也就是在condition队列中
	    static final int CONDITION = -2;
	    
	    // 表示当前场景下后续的acquireShared能够得以执行
	    static final int PROPAGATE = -3;
	    
	    // 默认值为0,表示当前节点在sync队列中,等待着获取锁
	    volatile int waitStatus;
	    
	    // 前驱节点,比如当前节点被取消,那就需要前驱节点和后继节点来完成连接
		volatile Node prev;
		
		// 后继节点
		volatile Node next
		
		// 存储condition队列中的后继节点
		volatile Thread thread;
		
		// 入队列时的当前线程
    	Node nextWaiter;
    }
        // 原子性的更新state
        protected final boolean compareAndSetState(int expect, int update) {
            // See below for intrinsics setup to support this
            return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
        }
         // 原子性的更新头结点
        private final boolean compareAndSetHead(Node update) {
            return unsafe.compareAndSwapObject(this, headOffset, null, update);
        }
        // 原子性的更新尾节点
        private final boolean compareAndSetTail(Node expect, Node update) {
            return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
        }
        // // 原子性的更新节点等待状态
        private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {
            return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);
        }
        // // 原子性的更新指定节点的下一个节点
        private static final boolean compareAndSetNext(Node node,Node expect,Node update) {
            return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
        }

获取锁的实现逻辑:

	// 获取锁的起始方法,在ReentrantLock中由lock()方法调用
    public final void acquire(int arg) {
    	// 先尝试获取锁,是否失败?
        if (!tryAcquire(arg) &&
        	// addWaiter(Node.EXCLUSIVE):将当前线程构造成一个队列放入道CLH队列尾部
        	// 在CLH队列中会检测当前线程构造的节点是否为head的直接后继,并尝试获取锁
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            // 中断当前线程
            selfInterrupt();
    }
    
    // 尝试获取锁状态,这是实际获取锁的实现逻辑。
    // 这个由具体子类实现获取锁状态的逻辑,比如在ReentrantLock中有FairSync(公平)和NonfairSync(非公平,默认)的不同实现。
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
    
    // 在CLH队列中会检测当前线程构造的节点是否为head的直接后继,并尝试获取锁
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            		// 当前线程构建的节点是否有前置节点,没有则抛出NullPointerException
                    final Node p = node.predecessor();
                    // 是否是头结点并且获取锁状态成功
                    if (p == head && tryAcquire(arg)) {
                    	// 将当前线程构建的节点设置为头结点
                        setHead(node);
                        // 将原本头结点的后继节点引用消除,用来回收原头结点
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;
                    }
                    // 在这里需要根据当前线程构建节点的前继节点waitStatus状态来判断是否要阻塞。
                    // 1、前驱节点为SIGNAL状态,在释放锁的时候会唤醒后继节点,所以后继节点(也就是当前节点)现在可以阻塞自己。
				 	// 2、前驱节点为CANCEL,向前遍历,更新当前节点的前驱为往前第一个非取消节点,当前节点会会再次回到循环并尝试获取锁。	
					// 3、等待状态为0或者PROPAGATE,设置前驱的等待状态为SIGNAL,并且之后会回到循环再次重试获取锁。
                    // 如果未成功获取锁则根据前驱节点判断是否要阻塞
                    // 如果阻塞过程中被中断,则置interrupted标志位为true
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }
    	
    	// 将当前线程构造成一个队列放入道CLH队列尾部
        private Node addWaiter(Node mode) {
        	// 构建一个节点,参数为当前线程与锁模式(排他还是共享)
            Node node = new Node(Thread.currentThread(), mode);
            // Try the fast path of enq; backup to full enq on failure
            // 先获取尾节点
            Node pred = tail;
            // 尾节点不为空?
            if (pred != null) {
            	// 将当前节点的前继节点设为尾节点,其实也就是
                node.prev = pred;
                // 原子性的将当前节点设为尾节点
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            // 初始情况或者在快速尝试失败后插入节点
            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;
                }
            }
        }
    }

释放锁的实现逻辑:

	// 释放锁的起始方法,在ReentrantLock中由unlock()调用
    public final boolean release(int arg) {
    	// 释放锁,释放成功?
        if (tryRelease(arg)) {
        	// 若释放锁成功,先获取头结点的引用
            Node h = head;
            // 如果头结点不为空切头结点的waitStatus不等于0
            if (h != null && h.waitStatus != 0)
            	// 唤醒后继结点包含的线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    // 是否锁的逻辑,实际有子类去做不同的实现
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
    
    // 唤醒后继节点包含的线程
    private void unparkSuccessor(Node node) {
		// 获取节点的等待状态
        int ws = node.waitStatus;
        // 是否小于0
        if (ws < 0)
        	// 将其更新为0
            compareAndSetWaitStatus(node, ws, 0);
    	// 获取节点的后继节点
        Node s = node.next;
        // 若后继节点为null 或  后继节点的等待状态为CANCEL(大于0就只能是CANCEL)
        if (s == null || s.waitStatus > 0) {
            s = null;
            // 从尾节点开始向前遍历
            for (Node t = tail; t != null && t != node; t = t.prev)
            	// 如果节点的等待状态不为CANCEL
                if (t.waitStatus <= 0)
                	// 将参数节点的后继节点设为此节点
                    s = t;
        }
        // 若参数节点的后继节点为null
        if (s != null)
        	// 
            LockSupport.unpark(s.thread);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值