深度解析:AQS原理

1.AQS简介

AQS是一个抽象类AbstractQueuedSynchronizer(抽象队列同步器)。它是基于等待队列用来实现同步锁(ReentrantLock,Semaphore,CountdownLatch,CyclicBarrier,Exchanger等等)核心组件的基础框架,它本身没有实现任何的同步接口,只是定义了获取以及释放同步状态的方法来提供自定义的同步组件。

2.实现原理

AQS中定义了一个使用volatile修饰state变量,用来标志同步状态,并保证线程之间的可见性。还定义了一个同步队列(使用Node的双向链表来实现)来保存竞争锁失败后的线程信息。

首先看下state变量:

 private volatile int state;

当线程cas修改state的值成功后,设置加锁线程为当前线程,如果失败,则把失败的线程信息加入到同步队列。

2.2Node

Node是AQS中的一个内部类:AQS使用Node保存竞争锁失败的线程信息。
Node结构如下:

static final class Node {
        volatile int waitStatus;//线程节点状态
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;//锁模式的标志
        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;
        }
    }

首先说明一下Node通过Node.mode节点定义了两种资源共享的方式,独占模式和共享方式。

        /** 表示这个节点是在共享模式下 */
		static final Node SHARED = new Node();
		/** 表示该节点是独占模式 */
		static final Node EXCLUSIVE = null;
		//通过nextWaiter 判断锁模式
		final boolean isShared() {
            return nextWaiter == SHARED;
        }

其中waitStatus代表每个线程的状态,它有如下值:

		 /** 线程已经被取消 */
		static final int CANCELLED =  1;
		/** 线程需要去被唤醒:获取同步状态失败,就挂起线程 */
		static final int SIGNAL    = -1;
		/** 表示当前节点在condition队列*/
		static final int CONDITION = -2;
		/* 线程的共享锁应该被无条件向下传播*/
		static final int PROPAGATE = -3;
		//0:无状态,表示当前节点在队列中等待获取锁

值得说一下的是,如果刚开始看并发相关的源码,很容易看着看着,就把state与Node节点的waitStatus搞混了,所以首次看注意下他们的区别。

3.实现源码

3.1获取锁

	//独占锁模式下获取锁
	public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
   //共享模式下获取锁
  public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
   }
   //其实共享模式下更多的还是使用此方法获取锁
   public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

AQS在不同锁模式下使用不同的方法来获取锁,比如独占锁中ReentrantLock.lock();是使用的acquire()方法实现,共享锁中的CountDownLatch.countDown()方法就是使用的acquireSharedInterruptibly()方法实现的。
其中tryAcquire(arg)、tryAcquireShared(arg)分别是独占模式下和共享模式下获取锁的方法,都由子类实现具体的获取锁逻辑。

3.2释放锁

//独占模式下的释放锁过程
 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
 }
//共享模式下的释放锁过程
public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
}

AQS在不同锁模式下使用不同的方法来释放锁,比如独占锁中ReentrantLock.unlock();是使用的release()方法实现,共享锁中的CountDownLatch.countDown()方法就是使用的releaseShared()方法实现。
其中tryRelease(arg)、tryReleaseShared(arg)分别是独占模式下和共享模式下释放锁的方法,都由子类实现具体的释放锁的逻辑。

4.独占锁和共享锁的区别

其实这里说锁的概念,在独占模式中还能理解,但是在共享模式中调用countDown()方法再说获取锁就能难理解。所以我感觉这里说state是一个许可更为容易理解。在独占模式下有线程获取到许可的过程就是state值从0=>1的过程,只有获取到许可的线程进行,重入的操作state值会从1=>2,在共享模式下当调用countDown()方法时,就是一个线程获取到一个许可,state的值-1,只有当许可消耗完,也就是state=0时才会恢复队列中挂起的线程。

5.总结

从上面AQS获取锁和释放锁的源码中可以看到,在获取锁和释放锁的过程中,具体对锁的获取和释放都是由其具体的子类来实现的。这也是AQS的精髓之处。

下面我会写两篇博客来分析ReentrantLock和CountDownLath的源码,具体分析AQS在独占和共享模式下的具体实现。

ReentrantLock源码解析
CountDownLatch源码解析

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值