AbstractQueuedSynchronizer解析 1.8

AbstractQueuedSynchronizer简称AQS,即队列(CLH队列)同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件
在这里插入图片描述

下面先简单的介绍下AQS中比较重要的方法

//同步状态,使用者可以对这个状态进行自定义,一般情况下可以定义为
//0表示没有竞争,线程可以直接获取锁
//大于0的时候锁正在被占用
//对于可重入的独占锁,可以表示为重入的次数
//对于有凭证数量的共享锁,可以表示为凭证数量,获取一个减去1
private volatile int state;

//使用CAS设置state状态,该方法能够保证状态设置的原子性;
protected final boolean compareAndSetState(int expect, int update)

//独占式尝试获取同步状态,需要使用者重写,返回true表明获取成功
protected boolean tryAcquire(int arg)

//独占式尝试释放同步状态,需要使用者重写
protected boolean tryRelease(int arg)

//共享式尝试获取同步状态,需要使用者重写,返回大于等于0表示获取成功
protected int tryAcquireShared(int arg)

//共享式尝试释放同步状态,需要使用者重写
protected boolean tryReleaseShared(int arg)

//独占式获取同步状态,会调用tryAcquire(int arg)尝试获取,如果失败将会将当前线程加入同步队列
public final void acquire(int arg)

//与acquire(int arg)功能相同,该操作可被中断,抛出InterruptedException
public final void acquireInterruptibly(int arg)

//独占式获取同步状态,带上超时机制,如果nanosTimeout纳秒内获取同步状态返回true,否则返回false
public final boolean tryAcquireNanos(int arg, long nanosTimeout)

//共享式获取同步状态,因为不是独占式的,所以可以有多个线程获取同步状态
public final void acquireShared(int arg)

//共享式获取同步状态,可中断
public final void acquireSharedInterruptibly(int arg)

//共享式获取同步状态,带上超时机制
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)

//独占式释放同步状态,会调用tryRelease(int arg),如果失败返回false,成功将会唤醒队列中的第二个节点,第一个是头节点(一个空节点)
public final boolean release(int arg)

//共享式释放同步状态,会调用tryAcquireShared(int arg),如果失败返回false,成功将会唤醒队列中的第二个节点
public final boolean releaseShared(int arg)

AQS是一个队列同步器,它的工作只是在并发的环境下处理线程(同步状态竞争失败的线程)入队、线程挂起、线程唤醒、线程出队, 而涉及到需要同步获取状态的逻辑需要使用者自己去编写

以独占模式为例,获取同步状态的过程

在这里插入图片描述
从上图可以看出同步器的设计是基于模版方法模式的,也就是说,使用者需要继承同步器并重写指定的方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模版方法,而这些模版方法将会调用使用者重写的方法

在上图中模版方法是acquire
用户需要重写的方法是tryAcquire

以独占模式为例,释放同步状态的过程
在这里插入图片描述

下面开始源码部分,先看下Node类

//队列节点,存储等待同步状态的线程,以及一些状态
static final class Node {
        //表明该节点中的线程等待的是共享式同步
        static final Node SHARED = new Node();
        
		//表明该节点中的线程等待的是独占式同步
        static final Node EXCLUSIVE = null;
        
		//表明该节点被取消,按照顺序检查需要被唤醒的线程的时候会跳过该节点
        static final int CANCELLED =  1;
        
        //因为是CLH队列,节点会循环访问前驱节点的状态,以确定自身是否可以成功被唤醒,
        //当在前驱节点状态为SIGNAL时,表示自己可以放心挂起,等待前驱节点释放同步状态的时候将自己唤醒
        //详情见shouldParkAfterFailedAcquire
        static final int SIGNAL    = -1;
		
		//condition时使用,暂时略
        static final int CONDITION = -2;
        
        //表示下一个acquireShared应该无条件传播,具体后面分析
        static final int PROPAGATE = -3;
		
		//存储的线程
		volatile Thread thread;

		//和condition共用,在这里表示同步的模式 为null(独占)或者 SHARED(共享)
		Node nextWaiter;
}
//队列头节点,在这里头节点是一个空节点,在线程第一次入队的时候初始化,
//线程出队的时候会把自身节点设置成新的头节点来替代老的头节点
private transient volatile Node head;

//队列尾节点
private transient volatile Node tail;

独占模式

public final void acquire(int arg) {
		//先调用用户重写的tryAcquire,如果失败则将本线程放入同步队列中
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    //指明需要用户继承重写
protected boolean tryAcquire(int arg) {
      throw new UnsupportedOperationException();
}

匹配上面的流程图,这里先调用tryAcquire,如果失败再执行入队操作

看下入队操作的源码

//创建节点,传入节点的类型,这里是独占模式,所以传入Node.EXCLUSIVE
    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;
        //当尾节点不为空,说明队列已经初始化过了,那么直接通过cas将节点加入尾节点
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果尾节点为null 或者 cas失败了
        enq(node);
        return node;
    }
 
 //队列未初始化,或者cas入队失败的情况下调用该方法
 private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //如果未初始化队列,创建head,cas赋值
            if (t == null) { 
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	//如果是之前cas入队失败,这里循环cas 直到成功
            	//需要注意的细节,先赋值prev,再通过cas赋值next,这样保证了无论cas成功还是失败,队列都是完整的
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }   

入队完成后,独占模式下,需要对已入队的节点进行一系列的处理

//对已入队的线程进行循环处理(不响应interrupted)
    //1、判断自己的前驱是否是头节点,如果是执行tryAcquire,成功则获取同步状态出队,否则2
    //2、修改自己前驱状态为SIGNAL(如果前驱状态为cancel则略过),成功则将自己挂起等待下次唤醒,否则3
    //3、如果前面都是否,说明在此期间节点发生了变化,那么进入循环进行下一轮判断
    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;
                }
                //修改自己前驱状态为SIGNAL,处理cancel节点,如果成功了
                //执行parkAndCheckInterrupt,将自己挂起,等待唤醒
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //如果parkAndCheckInterrupt也是true,说明该线程是通过中断唤醒的,那么保存中断标示
                    interrupted = true;
            }
        } finally {
            //如果失败,比如node.predecessor会抛出NullPointerException
            if (failed)
                //将节点设置成CANCELLED
                cancelAcquire(node);
        }
    }

//修改前驱状态为Node.SIGNAL,确保自己被挂起后,前驱能够叫醒自己
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        //如果已经是SIGNAL,则直接返回
        if (ws == Node.SIGNAL)
            return true;
            //如果>0,说明是cancel,那么这些节点将会被剔除出队列
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //如果前驱状态不为SIGNAL,则进行cas修改
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //如果前驱状态不为SIGNAL,则返回进入下一个循环
        return false;
    }
	
	//需要注意的是park是响应中断的,这个时候需要将中断标示保存下来返回
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

	//对节点执行取消操作
    private void cancelAcquire(Node node) {
        if (node == null)
            return;

        node.thread = null;

        //如果前驱节点也是cancel,剔除出队列
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;
        Node predNext = pred.next;

        node.waitStatus = Node.CANCELLED;

        //如果是尾节点则通过cas将自身剔除出队列
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            
            //如果前驱节点不为头节点 而且状态为signal 或者可以被cas修改成signal,后继节点不为cancel
            //则尝试将前驱节点直接关联后继节点,将自身剔除出队列
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                //如果修改signal失败,或者为头节点,则唤醒后继节点
                //这个时候唤醒对后继节点会执行shouldParkAfterFailedAcquire
                //来剔除cancel节点和设置前驱节点状态为SIGNAL
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }
	

到这里acquire部分就基本说完了,下面来看下独占模式下释放同步状态

public final boolean release(int arg) {
		//这里tryRelease也是需要使用者自己重写对
        if (tryRelease(arg)) {
            Node h = head;
            //如果头节点状态为0,说明后面没有节点了,否则唤醒后继节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }


    //唤醒node的后继节点
    private void unparkSuccessor(Node node) {
        
        int ws = node.waitStatus;
        if (ws < 0)
            //先要修改节点状态,正常情况下这里ws状态应该是SIGNAL,标示后继节点需要唤醒
            //而这里已经在进行唤醒操作,所以修改成0
            compareAndSetWaitStatus(node, ws, 0);
            
        Node s = node.next;
        //当遇到后继节点为空或者为cancel的情况下
        if (s == null || s.waitStatus > 0) {
            s = null;
            //从tail开始循环往前找最前一个状态不为cancel的节点
            //至于为什么不从前往后找,因为addWaiter中是先设置node.prev = pred;再设置pred.next = node;
            //所以从前往后找,也许找不到这个节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //将后继节点唤醒,这个时候被唤醒的线程开始继续走下一个循环,详细看acquireQueued
        if (s != null)
            LockSupport.unpark(s.thread);
    }

关于acquireInterruptibly, 与acquire的区别是,前者是响应中断,会抛出InterruptedException

public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

//和acquireQueued逻辑差不多
private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //唯一的区别在这里,这里检测到中断就会直接抛错
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

关于tryAcquireNanos,可以理解成带上超时机制的acquire,而且响应中断

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

//大致逻辑还是差不多的
private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        //先计算超时的时间界限,单位纳秒
        final long deadline = System.nanoTime() + nanosTimeout;
        //添加节点
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                //前面尝试获取同步状态失败后,开始计算剩余多久超时
                nanosTimeout = deadline - System.nanoTime();
                //如果时间小于0,说明已经超时了 直接退出
                if (nanosTimeout <= 0L)
                    return false;
                //如果剩余时间大于1000纳秒就用超时机制的挂起
                //如果剩余时间小于1000,就直接进入自旋模式,进入下一个循环继续判断
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                //响应中断
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            //如果超时了,那么该节点就变成cancel了
            if (failed)
                cancelAcquire(node);
        }
    }

这里基本上将独占模式的获取同步状态、释放同步状态讲完了,接下来看看共享模式部分
先来看看共享式获取同步状态

//和独占模式的区别在于,这里tryAcquireShared返回的不是boolean值了,小于0被认为同步失败
public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

//逻辑部分和独占模式一样
    private void doAcquireShared(int arg) {
    	//添加节点模式为SHARED
        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; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

	//这个方法做了俩件事
    //1、设置传入的节点为头节点
    //2、查看头节点的状态如果是signal或者PROPAGATE,则继续唤醒下一个节点
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        // 虽然是共享锁,但是为了避免不必要的唤醒,
        // 只有当头节点是signal或者PROPAGATE状态时才唤醒下一个节点
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            //判断下一个节点是否是共享节点
            if (s == null || s.isShared())
                //唤醒下一个节点
                doReleaseShared();
        }
    }

共享模式允许多个线程同时获取同步状态,所以在队列中等待的线程在被唤醒并且成功获取同步状态的时候,会换新下一个节点, 这就是PROPAGATE的含义

最后来看下doReleaseShared部分

private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                //当状态为SIGNAL时,表示可以唤醒下一个节点
                if (ws == Node.SIGNAL) {
                    //这里通过cas设置为0是为了防止多个线程同时执行了unparkSuccessor
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;  
                    //          
                    unparkSuccessor(h);
                }
                //如果发现已经有线程将状态设置为0了,则设置成PROPAGATE
                //如果设置不成功则循环继续设置,也就是说这里头节点的状态最终只会是SIGNAL或者是PROPAGATE
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            //当发现头节点没有变过,就可以安全的退出了
            if (h == head)                   // loop if head changed
                break;
        }
    }

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值