AQS之Condition刨根问底拦不住~

相关注释源代码:https://github.com/lhj502819/jdk1.8-source-analysis

为什么会有Condition这个东东?

显示的Lock与synchronized的功效相同,都是为执行逻辑加锁。Object 的监视器方法:wait、notify、notifyAll 应该都不陌生,在多线程使用场景下,必须先使用 synchronized 获取到锁,然后才可以调用 Object 的 wait、notify。
Condition 的使用,相当于用 Lock 替换了 synchronized,然后用 Condition 替换 Object 的监视器方法。具体如何替代的下方将会一一解析。

使用场景

举一个贴近生活的例子吧,例如我们排队去上厕所,每个厕所有专门负责发放卫生纸的,我们通过排队最终获得了进入了厕所,但是不巧的是发现忘记带纸,遇到这种事情很无奈,但是也得接受这个事实,这时只能乖乖的出去等着发纸员拿纸来**(Condtion#await)**,但其他人也有没拿纸的,需要等着发纸员把纸给前边的人(也就是进入了Condition条件队列中等待),当然自己再出厕所去拿纸之前还要把锁释放掉,好让后面排队的人进来,在发纸员给了纸之后(条件满足Condition#signal)自己再去厕所门口排队(AQS同步队列),等待获取锁。

//Person线程
//上厕所,获取锁
person.lock();
//获取成功
//发现没带纸
//释放锁去等着发纸员发纸,进入条件队列
person.await();
//拿到纸了
//继续去厕所排队拉粑粑
=============================================
//发纸员线程
//从条件等待队列中叫来第一个人,发给他纸
paper.signal()

示例

class ConditionDemo {
    final Lock lock = new ReentrantLock();
    //定义两个条件
    //当消费者消费之后调用notFull.signal通知生产者队列现在有空余位置了,可以继续消费
    //队列无数据的时候,消费者调用notEmpty.await
    final Condition notFull  = lock.newCondition();
    //当队列满的时候,生产者调用notFull.await阻塞生产
    //当生产者写入队列数据后,调用notEmpty.signal通知消费者队列里有数据了,可以继续生产
    final Condition notEmpty = lock.newCondition();

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

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            // 当count等于数组的大小时,当前线程等待,直到notFull通知,再进行生产
            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 {
            // 当count为0,进入等待,直到notEmpty通知,进行消费。
            while (count == 0)
                notEmpty.await();
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal();
            return x;
        } finally {
            lock.unlock();
        }
    }
}

大致的工作流程图如下:
AQS之Condition刨根问底拦不住~_第1张图片

原理拆解

await()方法

与Object#wait方法类似。

大致流程如下:

  1. 将当前线程加到Condition的条件队列中去,对应我们例子中的去排队和发纸员要纸一样;
  2. 释放当前线程获取的所有的锁,也就是将AQS的state字段置为0
  3. 将当前通过LockSupport挂起,等待其他线程调用#signal/#signalAll唤醒
  4. 唤醒后调用AQS#acquireQueued尝试获取锁,获取成功则继续执行,获取失败则可能还会被阻塞在AQS的同步队列中,等待后边再次获取锁。AQS的同步队列和Condition的条件队列的区别后边我们会讲解。

详细实现流程如下:
AQS之Condition刨根问底拦不住~_第2张图片

signal()方法

与Object#notify类似

大致流程如下:

  1. 获取当前Condition条件队列的头节点(等待时间最长的节点);
  2. 将头节点插入到AQS同步队列的尾部中;
  3. 将头节点的前一个节点状态设置为SIGNAL,这里唤醒后的节点则会执行await方法的第四步(在节点尝试获取锁时,如果节点不符合获取锁的条件,如果节点的前一个节点状态是SIGNAL,那当前节点则会被阻塞挂起,具体请看AQS#acquiredQueued);
  4. 返回成功成功

详细实现流程如下:
AQS之Condition刨根问底拦不住~_第3张图片

signalAll方法

signal方法不同的是,signalAll会将条件队列中的所有线程按照先后顺序添加到AQS的同步队列中。
每个线程添加之后都会尝试去获取锁,这里如果是使用的非公平锁就无法保证获取锁的先后顺序了。

AQS同步队列与Condition的条件队列的区别

宏观层面

每个线程通过await将自己阻塞后,都会进入Condition的条件队列中,如果被其他线程通过调用signal/signalAll会将当前线程放入AQS的同步队列中(同步队列中的线程才有机会再次获取锁),随后会将线程唤醒。

微观层面

条件队列中的Node与AQS中的Node是同一个Node,但使用的指针不同,因此互不影响。

  • AQS同步队列使用pre、next两个变量当做队列的指针,队列是双向的;
  • Condition条件队列使用nextWaite当做队列的指针,单向的。

AQS之Condition刨根问底拦不住~_第4张图片

你可能感兴趣的:(Java并发,java)