白话java锁--AQS--FairSync&NonfairSync、Condition、中断

上一篇文章分析了AQS的基本思想,此篇文章将对AQS中的其他方法,和其他高阶用法进行分析

AQS–ReentrantLock–FairSync&NonfairSync

上一篇文章只讲了ReentrantLock中的FairSync,也就是公平锁,没有分析NonfairSync,那么NonfairSync与FairSync有什么区别呢?

FairSync&NonfairSync

static final class FairSync extends Sync {
	private staticfinal long serialVersionUID = -3000897897090466540L;
	
	final void lock() {
	    acquire(1);
	}
	
	/**
	 * Fair version of tryAcquire.  Don't grant access unless
	 * recursive call or no waiters or is first.
	 */
	protected final boolean tryAcquire(int acquires) {
	    final Thread current = Thread.currentThread();
	    int c = getState();
	    if (c == 0) {
	        if (!hasQueuedPredecessors() &&
	            compareAndSetState(0, acquires)) {
	            setExclusiveOwnerThread(current);
	            return true;
	        }
	    }
	    else if (current == getExclusiveOwnerThread()) {
	        int nextc = c + acquires;
	        if (nextc < 0)
	            throw new Error("Maximum lock count exceeded");
	        setState(nextc);
	        return true;
	    }
	    return false;
	}
}
static final class NonfairSync extends Sync {
	private staticfinal long serialVersionUID = 7316153563782823691L;
	
	/**
	 * Performs lock.  Try immediate barge, backing up to normal
	 * acquire on failure.
	 */
	final void lock() {
	    if (compareAndSetState(0, 1))
	        setExclusiveOwnerThread(Thread.currentThread());
	    else
	        acquire(1);
	}
	
	protected final boolean tryAcquire(int acquires) {
	    return nonfairTryAcquire(acquires);
	}
}

首先非公平锁在进行加锁的时候会先尝试修改state状态值,如果修改成功,直接设置当前持有锁的线程为当前线程,如果不成功,才会调用父类AQS中的acquire方法

从这里,我们就可以看出公平锁和非公平锁的区别了,通过前一篇文章的分析,我们知道公平锁是要先判断state的状态,如果state不为0,或者不可重入的时候,当前线程才有机会竞争,但是非公平锁不同,非公平锁上来就尝试获取锁,一个是有先来后到关系,一个是不管你是谁,上来我就要获得锁

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

调用实现类非公平锁的tryAcquire方法

	protected final boolean tryAcquire(int acquires) {
	    return nonfairTryAcquire(acquires);
	}

调用Sync的nonfairTryAcquire方法

	final boolean nonfairTryAcquire(int acquires) {
	    final Thread current = Thread.currentThread();
	    int c = getState();
	    if (c == 0) {
	        if (compareAndSetState(0, acquires)) {
	            setExclusiveOwnerThread(current);
	            return true;
	        }
	    }
	    else if (current == getExclusiveOwnerThread()) {
	        int nextc = c + acquires;
	        if (nextc < 0) // overflow
	            throw new Error("Maximum lock count exceeded");
	        setState(nextc);
	        return true;
	    }
	    return false;
	}

可以看到这个方法与公平锁的方法十分类似,但是只有一个不同,就是在判断state为0的时候,不会查看等待队列是否为空,或者当前线程是否在第一位,也就是说等待队列中的头节点和当前线程拥有同样的机会获得state状态,获得锁,得到执行机会,这就是非公平锁的特性,不管先来后到,都有机会得到执行

FairSync&NonfairSync–对比总结

通过代码发现,只有两个区别:

  • 第一步lock()的时候,查看是否能够拥有执行机会
  • 第一步lock()失败的时候,再次判断是否拥有执行机会,如果还不能获得到锁,那么将按照AQS的模板方法进行入队操作

非公平锁的性能会更好一些,因为如果和队列中的头结点竞争成功了,那么就不需要再进行后续入队操作了(因为入队列还需要对线程进行阻塞和唤醒,就涉及线程的内核态与用户态的转换),但是可能会产生线程饥饿的问题,就是队列中的节点一直抢不到锁

通过以上分析,解决了我之前一直以来的误区,就是我以为非公平锁不管队列还是新来的线程都有获得锁的机会,实际上,只是新来的线程和队列的头节点之间的竞争,队列后面的节点还是要安装顺序一个一个或者执行机会

AQS–ReentrantLock–Condition

在AQS中有一个内部类,ConditionObject实现了Condition接口,并且在Condition的注释中写了一个例子

class BoundedBuffer {
    final Lock lock = new ReentrantLock();

    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await();
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await(); 
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

通过这个例子,帮助我们更好的理解。

上面的例子就是一个生产者-消费者的问题

ReentrantLock–Condition–创建

通过上面的例子可以知道,创建一个condition,直接调用ReentrantLock的newCondition方法即可

    public Condition newCondition() {
        return sync.newCondition();
    }

在Sync中就是

	final ConditionObject newCondition() {
	    return new ConditionObject();
	}

实际上就是实例化AbstractQueuedSynchronizer中的内部类ConditionObject

ConditionObject中有两个属性

	/** First node of condition queue. */
	private transient Node firstWaiter;
	/** Last node of condition queue. */
	private transient Node lastWaiter;

通过名字和注释也可以很好理解,一个是头节点,一个是尾结点

这里先讲一下,类似于AQS中的阻塞队列,AQS中同样存在一个队列,专门存储条件节点的队列,叫做条件队列,先大概画一下就是
在这里插入图片描述
并且条件队列可以存在多个,就像例子中的notFull和notEmpty,而且每个节点类型是Node的,执行notFull.await()方法,将会将线程阻塞,直到调用notFull.signal();才会将线程唤醒

ReentrantLock–Condition–await()

	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)) {
	        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);
	}

首先通过异常和判断,我们可以知道,这个方法是支持线程中断的,先判断当前线程的状态,如果这个线程的中断状态别设置,那么将抛出InterruptedException异常,从而从阻塞中唤醒(另一个不支持中断的是awaitUninterruptibly方法)

之后调用addConditionWaiter方法

	private Node addConditionWaiter() {
	    Node t = lastWaiter;
	    // If lastWaiter is cancelled, clean out.
	    if (t != null && t.waitStatus != Node.CONDITION) {
	        unlinkCancelledWaiters();
	        t = lastWaiter;
	    }
	    Node node = new Node(Thread.currentThread(), Node.CONDITION);
	    if (t == null)
	        firstWaiter = node;
	    else
	        t.nextWaiter = node;
	    lastWaiter = node;
	    return node;
	}

首先判断ConditionObject中的属性lastWaiter是否为空,并且判断这个节点的waitStatus属性是不是CONDITION,因为这个lastWaiter的状态可能已经是CANCELLED的,所以需要将这个Node移除这个条件队列,执行unlinkCancelledWaiters方法,重新获得lastWaiter

unlinkCancelledWaiters会之后下面继续分析,先看大体的流程

判断完之后开始将当前线程进行包装,包装成Node类型的数据,只不过在构造方法里面传的是CONDITION,代表这个Node为条件节点

之后判断lastWaiter是否为空,如果为空,说明最后一个节点都没有,代表当前队列为空的,那么就将这个新生成的节点当头节点,设置firstWaiter的属性,如果不为空,说明这个队列不为空,设置lastWaiter的nextWaiter为当前节点

并且将当前node设置为lastWaiter,并将当前包裹线程的node返回
在这里插入图片描述
我们可以看到条件队列是一个单向列表,新增的节点会一点一点添加到队列的尾部

接下来看一下unlinkCancelledWaiters这个方法做了什么

	private void unlinkCancelledWaiters() {
	    Node t = firstWaiter;
	    Node trail = null;
	    while (t != null) {
	        Node next = t.nextWaiter;
	        if (t.waitStatus != Node.CONDITION) {
	            t.nextWaiter = null;
	            if (trail == null)
	                firstWaiter = next;
	            else
	                trail.nextWaiter = next;
	            if (next == null)
	                lastWaiter = trail;
	        }
	        else
	            trail = t;
	        t = next;
	    }
	}

首先获得firstWaiter,也就是头节点,赋给局部变量t,判断t是否为空,如果不为空,获得t的nextWaiter,判断t的waitStatus是否为CONDITION类型的

  • 如果是,赋给临时变量trail,然后将下一个节点的变量赋值给t,进行下一次while(true)循环,判断下一个节点的waitStatus
  • 如果不是,说明这个节点被取消了,先将这个节点的nextWaiter,也就是下一个节点设置为空,判断tail是否为空,通过如果为是的情况判断,我们知道trail实际上存储的是一个临时变量,也就是上一个节点,如果为头节点的时候为空,那么为空的时候设置firstWaiter为当前节点的下一个节点,如果不为空,那么设置trail的下一个节点为当前节点的下一个节点,最后再判断next是否为空,如果为空,说明当前节点为最后一个节点,将trail设置为lastWaiter,也就是上一个节点设置为lastWaiter

通过上面方法的遍历,就将条件队列中的不是CONDITION的数据移除掉了

回过头来await方法,将当前线程加入到条件队列之后调用fullyRelease方法

    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

通过这个方法,会将state的设置为0,也就是完全释放锁,由于ReentrantLock是可重入的,所以savedState可能大于1,所以需要全部释放掉

这里需要注意的就是,在调用condition的await()方法的时候,需要先获得锁,也就是需要先调用lock()方法,设置完state值后才能调用条件方法,如果没有先加锁的行为,那么在执行fullyRelease方法的时候会抛出IllegalMonitorStateException异常,并将当前node的waitStatus设置为CANCELLED,标记为取消状态

再看await方法下面的部分

	while (!isOnSyncQueue(node)) {
	    LockSupport.park(this);
	    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
	        break;
	}
    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        return findNodeFromTail(node);
    }

刚看到这个while(true)可能会有点蒙,我们先看第一个判断,判断当前节点waitStatus的状态是否是CONDITION,此时,状态当然是CONDITION的了,那么返回false,进入while(true)循环后,LockSupport.park(this);,阻塞当前线程,那么什么时候会唤醒继续执行呢?

当然是在调用signal方法的时候,会继续这个条件,也就是上面生产者-消费者模型中的,如果队列满了则挂起,如果消费者消费完毕了,则会通过signal方法告诉消费值可以继续放入对象

下面看一下signal方法

	public final void signal() {
	    if (!isHeldExclusively())
	        throw new IllegalMonitorStateException();
	    Node first = firstWaiter;
	    if (first != null)
	        doSignal(first);
	}

在Sync中调用isHeldExclusively

	protected final boolean isHeldExclusively() {
	    // While we must in general read state before owner,
	    // we don't need to do so to check if current thread is owner
	    return getExclusiveOwnerThread() == Thread.currentThread();
	}

判断当前线程是否是持有锁的线程,只有持有锁的线程才能进行唤醒操作

获得第一个节点firstWaiter,执行doSignal

	private void doSignal(Node first) {
	    do {
	        if ( (firstWaiter = first.nextWaiter) == null)
	            lastWaiter = null;
	        first.nextWaiter = null;
	    } while (!transferForSignal(first) &&
	             (first = firstWaiter) != null);
	}

首先将当前节点的下一个节点赋值给firstWaiter,如果下一个节点为空,说明此队列就头节点一个节点,将lastWaiter设置为空,并将当前节点的下一个节点设置为空

进行完以上操作之后,相当于将当前节点移除条件队列了,之后执行transferForSignal方法

    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

尝试修改node的waitStatus的值,修改为0,如果修改不成功,说明这个节点已经被取消了,那么返回false后,跳回到while(true)循环的下一步,将firstWaiter赋值给临时变量first,进行下一个节点的判断

如果修改成功,执行enq方法,通过昨天的分析,我们知道这个方法是将当前节点加入到阻塞队列中,并返回当前节点的上一个节点

    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;
                }
            }
        }
    }
  • 如果上一个节点waitStatus大于0,说明上一个节点取消了,唤醒当前线程
  • 如果上一个线程小于等于0,说明上一个节点可用,但是在设置waitStatus为SIGNAL失败了,同样唤醒当前线程

唤醒之后返回true,跳出while(true)循环,唤醒结束,通过以上步骤,我们可以知道,实际上唤醒就是将节点从条件队列移除,加入到阻塞队列中,并唤醒节点

现在回到await()方法

	while (!isOnSyncQueue(node)) {
	    LockSupport.park(this);
	    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
	        break;
	}

从这里将线程唤醒,继续执行checkInterruptWhileWaiting方法

	/**
	 * Checks for interrupt, returning THROW_IE if interrupted
	 * before signalled, REINTERRUPT if after signalled, or
	 * 0 if not interrupted.
	 */
	private int checkInterruptWhileWaiting(Node node) {
	    return Thread.interrupted() ?
	        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
	        0;
	}

通过注释我们可以简单了解一下,这个方法主要是判断中断,如果在调用signalled之前中断过,那么返回THROW_IE,如果在signalled之后,那么返回REINTERRUPT,或者没有被中断过,则返回0

那么这个是如何判断的呢?

首先调用Thread.interrupted()方法,如果返回true,说明这个线程已经被中断了,在这个方法调用之后会将线程的中断状态设置为false

其次再进行判断调用transferAfterCancelledWait方法

    final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin.
         */
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

首先判断node的waitStatus的值是否为CONDITION

  • 如果没有执行signal方法,那么这个node还在条件队列中,这个节点的waitStatus还是初始值CONDITION,如果成功,将这个节点加入到阻塞队列中,返回true
  • 如果执行了signal方法后,会将这个node的waitStatus修改为0,通过CAS失败,说明节点已经在阻塞队列中了,说明已经调用过signal方法了,因为存在虽然已经修改状态了,但是还没有加入到阻塞队列中的情况,所以这里用while(true)循环等待完全加入到阻塞队列之后返回false

这个Thread.yield();方法是让出CPU执行权限,但是当前线程可能还有执行机会

通过上面的步骤我们知道如果线程中断状态被设置了,那么就会从这个while(true)循环退出,那么为什么需要这么多的判断呢?

因为LockSupport.park(this);继续执行有几种情况

  • 正常通过signal方法,唤醒当前线程
  • 如果另一个线程对这个线程进行了中断
  • 执行signal方法的时候上一个节点不可用或者CAS失败,在transferForSignal方法里面

适用场景:假如node处于条件队列中,但是另外一个线程将这个线程中断了,没有被signal唤醒,那么就会将这个节点加入到阻塞队列中,进行阻塞队列的逻辑

所以上面这么多麻烦的步骤主要是为了判断另一个线程如果对这个线程进行了中断,不同的时期会有不同的处理方式,但是不管怎么样,这个条件队列的node都被假如到阻塞队列中了,继续看await方法

	if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
	    interruptMode = REINTERRUPT;
	if (node.nextWaiter != null) // clean up if cancelled
	    unlinkCancelledWaiters();
	if (interruptMode != 0)
	    reportInterruptAfterWait(interruptMode);

调用acquireQueued方法,第一个参数表示当前这个节点(刚从条件队列中转移来),第二个参数是savedState(可重入state的值)

    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);
        }
    }

通过前一篇文章的分析,我们知道节点进入阻塞队列,通过parkAndCheckInterrupt方法,返回线程中断状态

    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

如果返回true,说明线程被中断过,之后判断interruptMode != THROW_IE,如果不相等,说明在调用signal之前就已经中断,需要重新中断,设置interruptMode状态为REINTERRUPT

	if (node.nextWaiter != null) // clean up if cancelled
	    unlinkCancelledWaiters();

继续看判断,如果node的nextWaiter不为空,说明在调用signal方法的过程中的时候,线程被中断了,还没有将node的nextWaiter设置完成,线程就中断了,所以这个时候就需要重新进行清除,调用unlinkCancelledWaiters方法

	private void unlinkCancelledWaiters() {
	    Node t = firstWaiter;
	    Node trail = null;
	    while (t != null) {
	        Node next = t.nextWaiter;
	        if (t.waitStatus != Node.CONDITION) {
	            t.nextWaiter = null;
	            if (trail == null)
	                firstWaiter = next;
	            else
	                trail.nextWaiter = next;
	            if (next == null)
	                lastWaiter = trail;
	        }
	        else
	            trail = t;
	        t = next;
	    }
	}

将waitStatus不为CONDITION的节点移除

最后执行,判断interruptMode,是否被中断过,如果没有被中断过,那么interruptMode的值不为0

	if (interruptMode != 0)
	    reportInterruptAfterWait(interruptMode);
	private void reportInterruptAfterWait(int interruptMode)
	    throws InterruptedException {
	    if (interruptMode == THROW_IE)
	        throw new InterruptedException();
	    else if (interruptMode == REINTERRUPT)
	        selfInterrupt();
	}

通过之前的分析,我们知道:

  • 如果在await之后,signal之前发生中断的话interruptMode为THROW_IE,这种情况,需要抛出异常,告诉外面的业务逻辑,当前中断了,停止阻塞状态
  • 如果在signal之后发生了中断,代表await的时候没有中断,调用selfInterrupt();方法,线程自己决定是否抛出中断异常,因为此时已经和AQS没有关系了

ReentrantLock–Condition–await(long time, TimeUnit unit)

接下来看一下带超时的 await 方法

	public final boolean await(long time, TimeUnit unit)
	                throws InterruptedException {
	    long nanosTimeout = unit.toNanos(time);
	    if (Thread.interrupted())
	        throw new InterruptedException();
	    Node node = addConditionWaiter();
	    int savedState = fullyRelease(node);
	    final long deadline = System.nanoTime() + nanosTimeout;
	    boolean timedout = false;
	    int interruptMode = 0;
	    while (!isOnSyncQueue(node)) {
	        if (nanosTimeout <= 0L) {
	            timedout = transferAfterCancelledWait(node);
	            break;
	        }
	        if (nanosTimeout >= spinForTimeoutThreshold)
	            LockSupport.parkNanos(this, nanosTimeout);
	        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
	            break;
	        nanosTimeout = deadline - System.nanoTime();
	    }
	    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
	        interruptMode = REINTERRUPT;
	    if (node.nextWaiter != null)
	        unlinkCancelledWaiters();
	    if (interruptMode != 0)
	        reportInterruptAfterWait(interruptMode);
	    return !timedout;
	}

首先通过转换获得等待的纳秒时间,后面同样判断线程中断状态,将node加入条件队列,然后释放锁

	long nanosTimeout = unit.toNanos(time);
	if (Thread.interrupted())
	    throw new InterruptedException();
	Node node = addConditionWaiter();
	int savedState = fullyRelease(node);

然后计算最后的等待时间,同样通过while(true)循环判断当前节点是否在阻塞队列中

	final long deadline = System.nanoTime() + nanosTimeout;
	
	while (!isOnSyncQueue(node)) {

先判断nanosTimeout是否小于0,也就是超时时间,如果超时时间到了,那么调用transferAfterCancelledWait方法,将这个节点从条件队列中移除,加入到阻塞队列中

	if (nanosTimeout <= 0L) {
	   timedout = transferAfterCancelledWait(node);
	    break;
	}
    final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin.
         */
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

通过transferAfterCancelledWait这个方法的返回我们可以知道,如果是true,说明这个节点一直在条件队列中,这个节点到时间了,应该从条件队列里面出来,走阻塞队列的逻辑,如果返回false,说明已经在阻塞队列里面了,说明已经调用过signal方法了,说明没有超时

接下来判断nanosTimeout的大小是否比spinForTimeoutThreshold大,这个目的主要是为了如果等待时间比较小的话,阻塞线程反而会影响性能,这里会通过while(true)的方式进行判断,也就是自旋的方式,省去了内核态与用户态的转换

	if (nanosTimeout >= spinForTimeoutThreshold)
	    LockSupport.parkNanos(this, nanosTimeout);

之后逻辑与没有超时的逻辑相同了

	if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
		break;

重新计算超时时间

    nanosTimeout = deadline - System.nanoTime();

通过以上步骤,发现带有超时的方法无非就是在调用阻塞方法的时候,会带上一个超时时间的参数,如果超时会将这个节点移动到阻塞队列中,其他的没有什么区别。

	LockSupport.parkNanos(this, nanosTimeout);

ReentrantLock–Condition–awaitUninterruptibly()

这里再简单的看一下awaitUninterruptibly这个方法,就是不抛出InterruptedException异常的方法

	public final void awaitUninterruptibly() {
	    Node node = addConditionWaiter();
	    int savedState = fullyRelease(node);
	    boolean interrupted = false;
	    while (!isOnSyncQueue(node)) {
	        LockSupport.park(this);
	        if (Thread.interrupted())
	            interrupted = true;
	    }
	    if (acquireQueued(node, savedState) || interrupted)
	        selfInterrupt();
	}

实际上就是设置对应线程的中断状态保证不抛出异常的

AQS–cancelAcquire()

接下来说一下AQS如何让线程取消队列,cancelAcquire方法,那么什么情况下才会调用这个方法呢?

首先看一下acquire这个方法中的acquireQueued方法

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    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);
        }
    }
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

假如现在线程中断了,那么在调用线程的interrupted的方法会返回线程的中断状态为true,然后设置变量interrupted为true,然后再进行下一次while(true)判断的时候,重新获得锁竞争权限,如果竞争到了锁,那么在外层方法在进行返回的时候会执行selfInterrupt()方法,重新获得并设置线程的中断状态

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

通过以上分析好像也没有取消队列的操作啊,只是设置了中断状态而已,带着这样的想法我们来看

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
    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);
        }
    }

通过上面的调用链和代码逻辑,我们可以发现如果执行的是lockInterruptibly方法,那么如果线程被中断,会在doAcquireInterruptibly方法里面抛出InterruptedException异常,这时failed为true,执行cancelAcquire方法,所以只有在调用lockInterruptibly方法的时候,如果线程被中断才会有取消队列的操作

	private void cancelAcquire(Node node) {
		// Ignore if node doesn't exist
		if (node == null)
		    return;
		
		node.thread = null;
		
		// Skip cancelled predecessors
		Node pred = node.prev;
		while (pred.waitStatus > 0)
		    node.prev = pred = pred.prev;
		
		// predNext is the apparent node to unsplice. CASes below will
		// fail if not, in which case, we lost race vs another cancel
		// or signal, so no further action is necessary.
		Node predNext = pred.next;
		
		// Can use unconditional write instead of CAS here.
		// After this atomic step, other Nodes can skip past us.
		// Before, we are free of interference from other threads.
		node.waitStatus = Node.CANCELLED;
		
		// If we are the tail, remove ourselves.
		if (node == tail && compareAndSetTail(node, pred)) {
		    compareAndSetNext(pred, predNext, null);
		} else {
		    // If successor needs signal, try to set pred's next-link
		    // so it will get one. Otherwise wake it up to propagate.
		    int ws;
		    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 {
		        unparkSuccessor(node);
		    }
		
		    node.next = node; // help GC
		}
	}

首先判断node是否为空,设置当前node的thread为空,将当前node的上一个节点的perv设置为当前node的prev,直到找到可用的节点,设置当前节点的waitStatus为CANCELLED

  • 如果是tail节点,那么重新设置tail节点为上一个节点,并设置上一节点的next为空
  • 如果不是tail节点,判断是否为head,判断上一节点的waitStatus是否为SIGNAL,判断是否小于0并且设置上一节点的waitStatus为SIGNAL,判断上一节点的线程是否为空,如果上面的判断都通过了,那么就是设置上一节点的next为当前node的next

最后将当前节点的next指向自己,帮助垃圾清除器清除没有对象引用的对象

AQS–总结

通过上面的分析我们知道了公平锁和非公平锁实现的原理和底层代码
知道了condition在AQS中的使用方法,具体使用逻辑
知道了中断方法的两种处理方式

  • 可以通过抛出异常告诉外层业务逻辑
  • 也可以通过设置线程中断状态,告诉外层业务逻辑我中断过

外层业务逻辑再通过设置的中断状态或者异常进行具体的业务处理

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值