Java concurrent包源码走读(三)

在Java concurrent包源码走读(二)我们知道AQS中有个条件队列,但是具体它的作用是干什么、它和同步队列有个关系,接下来这篇我们来了解AQS中的条件队列。首先我们先看一下和条件队列关联的ReentrantLock类。

ReentrantLock

类图

Java concurrent包源码走读(三)_第1张图片

从类图中我们可以看到此类实现Lock,同时有个抽象类Sync,Sync这个类是继承AbstractQueuedSynchronizer,同时它也两个子类,FairSync和NonfairSync。由此可见ReentrantLock它可以公平的获取锁也可以非公平方式获取锁。我们通过源码还可以看出ReentrantLock特点:

  1. 互斥锁

  2. 支持公平和非公平获取锁,默认是非公平

  3. 可重入锁

  4. 支持条件变量(实现Lock接口)

对于ReentrantLock获取释放锁的源码我们就再分析,感兴趣的同学可以走读,主要看tryAcquire和tryRelease方法。走读的时候可以带着下面的两个问题?

  1. ReentrantLock如何实现可重入?

  2. ReentrantLock的Lock为何要需要try catch,并且lock需要在try的外面?

条件队列

我们主通过await和signal方法分析同步队列和条件队列的交互。

await()方法
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //将节点放入等待队列
    Node node = addConditionWaiter();
    //释放节点占的锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //轮询判断节点是否在AQS队列
    while (!isOnSyncQueue(node)) {
        //如果在则阻塞节点对应的线程
        //它是何时加入到AQS队列中呢?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);
}

signal()方法
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;
    //唤醒等待队列第一个节点,注意只是唤醒,竞争到锁的看AQS队列
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

通过上面源码的分析,我们知道AQS自己维护的队列是当前等待资源的队列,AQS会在资源被释放后,依次唤醒队列中从前到后的所有节点,使他们对应的线程恢复执行。直到队列为空。而条件队列维护一个等待signal信号的队列,两个队列的作用是不同,事实上,每个线程也仅仅会同时存在以上两个队列中的一个,流程是这样的:

  1. 线程1调用reentrantLock.lock时,线程被加入到AQS的等待队列中。

  2. 线程1调用await方法被调用时,该线程从AQS中移除,对应操作是锁的释放。

  3. 接着马上被加入到Condition的等待队列中,意味着该线程需要signal信号。

  4. 线程2因为线程1释放锁的关系,被唤醒,并判断可以获取锁,于是线程2获取锁,并被加入到AQS的等待队列中。

  5. 线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。注意,这个时候线程1并没有被唤醒

  6. signal方法执行完毕,线程2调用reentrantLock.unLock()方法,释放锁。这个时候因为AQS中只有线程1,于是,AQS释放锁后按从头到尾的顺序唤醒线程时,线程1被唤醒,于是线程1回复执行。

  7. 直到释放所整个过程执行完毕。

可以看到,整个协作过程是靠结点在AQS的等待队列和条件队列中来回移动实现的,条件队列维护了一个等待信号的节点,并在适时的时候将结点加入到AQS的等待队列中来实现的唤醒操作。

你可能感兴趣的:(Java concurrent包源码走读(三))