Java并发源码分析之Condition

关联文章:
关联文章:
Java并发源码分析之AQS及ReentrantLock
Java并发源码分析之Semaphore
Java并发源码分析之ReentrantReadWriteLock
Java并发源码分析之CyclicBarrier
Java并发源码分析之CountDownLatch

工作原理概要

在并发编程中,每个Java对象都有一组监视器方法,如wait()、notify()和notifyAll(),通过这些方法,可以实现等待唤醒机制,但是这些方法必须配合synchronized关键字使用。Condition同样通过await()、signal()、signalAll()实现了等待唤醒机制,相比原有的一组方法,Condition更加灵活。notify()唤醒的线程是随机的,而signal()可以控制唤醒的线程。
总结如下:
1、Condition可以精确控制多线程的休眠与唤醒
2、对于同一个锁,可以建立为多个线程建立不同的Condition

使用demo

这是一个生产者-消费者模式,生产者生产烤鸭,消费者消费烤鸭。对于生产者来讲,如果存在烤鸭,生产者停止生产,等待消费者消费。对于消费者来讲,如果没有烤鸭,停止消费,等待生产者生产。

public class ResourceByCondition {
    private String name;
    private int count = 1;
    // 当前有烤鸭的标志
    private boolean flag = false;

    //创建一个锁对象。
    Lock lock = new ReentrantLock();

    //通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
    Condition producerCondition = lock.newCondition();
    Condition consumerCondition = lock.newCondition();

    
    // 生产
    public void product(String name) {
        lock.lock();
        try {
            // while循环防止消费线程没有获取到锁, 从而无法消费
            // 消费者线程获取到锁, flag会更新成false
            while (flag) {
                try {
                    // 当前线程释放锁, 加入等待队列中等待
                    producerCondition.await();
                } catch (InterruptedException e) {
                }
            }
            this.name = name + ", 编号为: " + count;
            count++;
            System.out.println("生产者线程: "+Thread.currentThread().getName() + ", 生产: " + this.name);
            // 生产烤鸭完成, 更新标志为有
            flag = true;
            // 生产成功, 唤醒消费线程去消费
            consumerCondition.signal();
        } finally {
            lock.unlock();
        }
    }

    // 消费
    public void consume() {
        lock.lock();
        try {
            // 这里道理是一样的, 防止生产线程没有获取到锁
            while (!flag) {
                try {
                    consumerCondition.await();
                } catch (InterruptedException e) {
                }
            }
            //消费烤鸭
            System.out.println("消费者线程: " + Thread.currentThread().getName() + ", 消费: " + this.name);
            // 消费烤鸭完成, 更新标志为无
            flag = false;
            // 消费成功, 唤醒生产线程继续生产
            producerCondition.signal();
        } finally {
            lock.unlock();
        }
    }
}

public class Mutil_Producer_ConsumerByCondition {

    public static void main(String[] args) {
        ResourceByCondition r = new ResourceByCondition();
        Mutil_Producer pro = new Mutil_Producer(r);
        Mutil_Consumer con = new Mutil_Consumer(r);
        //生产者线程
        Thread t0 = new Thread(pro);
        Thread t1 = new Thread(pro);
        //消费者线程
        Thread t2 = new Thread(con);
        Thread t3 = new Thread(con);
        //启动线程
        t0.start();
        t1.start();
        t2.start();
        t3.start();
    }
}

//decrition 生产者线程
class Mutil_Producer implements Runnable {
    private ResourceByCondition resourceByCondition;

    Mutil_Producer(ResourceByCondition resourceByCondition) {
        this.resourceByCondition = resourceByCondition;
    }

    public void run() {
        while (true) {
            resourceByCondition.product("北京烤鸭");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// decrition 消费者线程
class Mutil_Consumer implements Runnable {
    private ResourceByCondition resourceByCondition;

    Mutil_Consumer(ResourceByCondition resourceByCondition) {
        this.resourceByCondition = resourceByCondition;
    }

    public void run() {
        while (true) {
            resourceByCondition.consume();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果为:
运行结果
可以看到生产消费依次进行。

主要方法

 /* 使当前线程进入等待状态,直到被通知(signal)或中断,相当于Object wait()方法
  * 当其他线程调用signal()或singnal()方法时,该线程被唤醒
  * 当其他线程调用interrupt()方法时,该线程被中断
  */ 
 void await() throws InterruptedException;
 // 唤醒一个在Condition上等待的线程,该线程从等待方法返回前必须获取与Condition关联的锁,相当于Object notify()方法
 void signal();
 // 唤醒所有在Condition上等待的线程,该线程从等待方法返回前必须获取与Condition关联的锁,相当于Object notifyAll()方法
 void signalAll();

原理概述

Condition的具体实现类是AQS的内部类ConditionObject,如下:

 // 等待队列中的第一个节点
 private transient Node firstWaiter;
 // 等待队列中最后一个节点
 private transient Node lastWaiter;

AQS中存在同步队列和等待队列,Condition使用的就是等待队列。等待队列是单向的,Node节点的nextWaiter指针指向队列中的后继节点,Condition通过firstWaiter和lastWaiter来表示队列中的头节点和尾节点。等待队列中节点的等待状态只有两种,CANCELLED和CONDITION,前者表示无效节点,需要被移除,后者表示节点等待被唤醒。
一个Condition代表一个等待队列,可以同时存在多个Condition,即存在多个等待队列,但是同步队列只有一个。

对比项同步队列等待队列
节点类型NodeNode
方向双向(prev、next)单向(nextWaiter)
首尾节点head(无效节点,不含有线程)、tailfirstWaiter、lastWaiter
等待状态(waitStatus)SIGNAL、CANCELLED、PROPAGATECONDITION、CANCELLED

Condition 进入等待状态解析

1、await进入等待状态入口

 public final void await() throws InterruptedException {
     // 如果当前线程被中断, 抛出异常
     if (Thread.interrupted())
         throw new InterruptedException();
     // 将当前线程封装成等待节点加入等待队列, 并返回
     Node node = addConditionWaiter();
     // 释放当前线程持有的锁, 即释放同步状态
     int savedState = fullyRelease(node);
     int interruptMode = 0;
     // 判断node节点是否在同步队列中
     while (!isOnSyncQueue(node)) {
         // 如果node节点不在同步队列中
         // 挂起线程
         LockSupport.park(this);
         // 判断是否被中断唤醒, 如果是则退出循环
         if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
             break;
     }
     //被唤醒后执行自旋操作争取获得锁,同时判断线程是否被中断
     if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
         interruptMode = REINTERRUPT;
     // 如果又有新的节点加入到等待队列中
     if (node.nextWaiter != null)
         // 清理等待队列中不为CONDITION状态的节点
         unlinkCancelledWaiters();
     // 中断模式不为0, 即发生过中断
     if (interruptMode != 0)
         // 根据中断模式判断, 如果为THROW_IE, 需要抛出异常, 如果为REINTERRUPT, 需要自我中断
         reportInterruptAfterWait(interruptMode);
 }

2、addConditionWaiter将当前线程封装成等待节点加入等待队列, 并返回

 private Node addConditionWaiter() {
     Node t = lastWaiter;
     // 如果等待队列中尾节点状态为结束状态, 即尾节点无效
     if (t != null && t.waitStatus != Node.CONDITION) {
         // 解绑无效的等待节点, 生成新的等待队列
         unlinkCancelledWaiters();
         // t赋值为新的尾节点
         t = lastWaiter;
     }
     // 将当前线程封装为等待节点
     Node node = new Node(Thread.currentThread(), Node.CONDITION);
     // 如果尾节点为null, 即等待队列为空, 将头节点赋值为当前节点
     if (t == null)
         firstWaiter = node;
     else
         // 尾节点不为null, 则将尾节点的后继指针指向当前节点
         t.nextWaiter = node;
     // 当前节点设置为新的尾节点
     lastWaiter = node;
     return node;
 }

3、unlinkCancelledWaiters解绑无效的等待节点, 生成新的等待队列

 private void unlinkCancelledWaiters() {
     Node t = firstWaiter;
     // 在循环中的新队列尾节点
     Node trail = null;
     while (t != null) {
         // 获取t的后继节点
         Node next = t.nextWaiter;
         // 如果t节点等待状态无效
         if (t.waitStatus != Node.CONDITION) {
             // 将t从链表中断开
             t.nextWaiter = null;
             // 新队列为空
             if (trail == null)
                 // 将next赋值给新的头节点
                 firstWaiter = next;
             else
                 // 新队列不为空, 将尾节点的后继节点指向next
                 trail.nextWaiter = next;
             // next为空, 即已经循环到最后一个节点了
             if (next == null)
                 // 将新队列的尾节点赋值为trail
                 lastWaiter = trail;
         }
         else
             // t节点为有效节点, trail赋值为t
             trail = t;
         // 继续下次循环
         t = next;
     }
 }

4、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;
     }
 }

5、isOnSyncQueue判断node节点是否在同步队列中

 final boolean isOnSyncQueue(Node node) {
     // 等待状态为CONDITION或没有prev(同步队列前驱节点指针), 则不在同步队列中
     if (node.waitStatus == Node.CONDITION || node.prev == null)
         return false;
     // 存在next(同步队列后继节点指针), 则在同步队列中
     if (node.next != null) 
         return true;
     // 如果node节点还未加入到队列中, 但是node的prev已经设置为同步队列中的节点了,即将加入队列中
     // 遍历同步队列查找
     return findNodeFromTail(node);
 }
 // 遍历同步队列查找
 private boolean findNodeFromTail(Node node) {
     // 从尾节点开始遍历, 找到节点返回true, 找不到返回false
     Node t = tail;
     for (;;) {
         if (t == node)
             return true;
         if (t == null)
             return false;
         t = t.prev;
     }
 }

如果节点node不在同步队列中,则说明node已经释放锁了,并且进入了等待队列,将线程挂起,然后等待唤醒即可。

 LockSupport.park(this);
 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
     break;

这段代码中,第一行已经将当前线程挂起了,暂时不会继续向下执行了。如果执行到第二行,说明当前线程被其他线程唤醒了,唤醒之后,需要先检查这段时间内当前线程是否被中断,保证线程安全。
如果线程被中断过,将node加入到同步队列中;如果未被中断,跳出循环。

6、checkInterruptWhileWaiting检查挂起期间是否被中断

 private int checkInterruptWhileWaiting(Node node) {
     // 如果线程被中断, 将其加入同步队列中, 加入失败返回需要抛出异常, 加入成功返回需要重新中断
     // 如果线程未被中断, 返回0
     return Thread.interrupted() ?
         (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
         0;
 }
 final boolean transferAfterCancelledWait(Node node) {
    // 将node的等待状态更新成0, 更新成功加入同步队列, 返回true
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        return true;
    }
    // 更新失败, 说明node节点的等待状态为取消状态, 如果不在同步队列中, 让出CPU执行权,则一直循环等待其他线程将node节点加入到同步队列中
    while (!isOnSyncQueue(node))
        Thread.yield();
    // 返回失败
    return false;
 }
 // 退出等待时重新中断, 从等待状态切换为中断状态
 private static final int REINTERRUPT =  1;
 // 退出等待时抛出InterruptedException
 private static final int THROW_IE    = -1;

如果线程为中断状态,会进入transferAfterCancelledWait,会将node的等待状态更新为初始状态0,更新成功,会加入到同步队列中。
如果更新失败,说明等待状态为取消状态,循环等待其他线程将node节点加入到同步队列中。

7、节点回到竞争状态,处理线程状态

 // 走到这里说明node节点已在同步队列中, 被唤醒后执行自旋操作争取获得锁,同时判断线程是否被中断
 // 申请到锁才会继续执行后续的代码
 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
      interruptMode = REINTERRUPT;
  // nextWaiter不为空,说明又有新的节点加入到等待队列中
  if (node.nextWaiter != null)
      // 清理等待队列中不为CONDITION状态的节点
      unlinkCancelledWaiters();
  // 中断模式不为0, 即发生过中断
  if (interruptMode != 0)
      // 根据中断模式判断, 如果为THROW_IE, 需要抛出异常, 如果为REINTERRUPT, 需要自我中断
      reportInterruptAfterWait(interruptMode);

节点加入到同步队列中,并开始自旋申请锁,就开始了正常获取锁的流程了。
接下来要处理的是线程状态,interruptMode != THROW_IE,线程获取锁成功之后才会走到这里。不等于异常,说明线程未被中断,或者等待状态已经由CONDITION更新为初始状态0,将interruptMode设置为REINTERRUPT,即未来还会让线程中断。

8、reportInterruptAfterWait处理中断状态

 private void reportInterruptAfterWait(int interruptMode)
     throws InterruptedException {
     // 需要抛出异常,则抛出异常
     if (interruptMode == THROW_IE)
         throw new InterruptedException();
     else if (interruptMode == REINTERRUPT)
     	 // 需要再次中断,则中断
         selfInterrupt();
 }

Condition 唤醒节点解析

1、signal唤醒节点入口

 public final void signal() {
     // 判断当前线程是否持有排它锁, 不是的话抛出异常
     if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     // 获取等待队列中的头节点
     Node first = firstWaiter;
     // 头节点不为null, 唤醒头节点
     if (first != null)
         doSignal(first);
 }

2、doSignal唤醒节点

 private void doSignal(Node first) {
     do {
         // 移除等待队列中的头节点, 将头节点的后继节点赋值为新的头节点
         // 如果新的头节点为空, 即等待队列只有一个节点
         if ( (firstWaiter = first.nextWaiter) == null)
             // 将新的尾节点设置为空
             lastWaiter = null;
         // 原头节点从队列中移除
         first.nextWaiter = null;
     // 唤醒头节点, 唤醒失败, 则继续向后取等待节点唤醒, 直至成功
     } while (!transferForSignal(first) &&
              (first = firstWaiter) != null);
 }

3、transferForSignal唤醒等待队列中的node节点

 // 唤醒等待队列中的node节点
 final boolean transferForSignal(Node node) {
     // 将node节点的等待状态更新为0, 更新失败, 说明node节点的状态为取消状态, 返回唤醒失败
     if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
         return false;
     // 将node节点加入同步队列, 返回node节点的前驱节点
     Node p = enq(node);
     // 获取前驱节点的等待状态
     int ws = p.waitStatus;
     // 如果ws大于0, 即前驱节点在同步队列为无效状态, 以后也无法唤醒node节点
     // 如果更新前驱节点等待状态失败, 以后也无法唤醒node节点
     if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
         // 直接唤醒node节点的线程, 申请锁
         LockSupport.unpark(node.thread);
     return true;
 }

Condition 唤醒所有节点解析

1、signalAll唤醒所有节点入口

 public final void signalAll() {
     // 判断当前线程是否持有排它锁, 不是的话抛出异常
     if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     Node first = firstWaiter;
     // 头节点不为null, 从头节点开始唤醒所有节点
     if (first != null)
         doSignalAll(first);
 }

2、doSignalAll唤醒所有节点

 private void doSignalAll(Node first) {
     lastWaiter = firstWaiter = null;
     // 队列不为空,循环唤醒等待队列中的所有节点
     do {
         Node next = first.nextWaiter;
         first.nextWaiter = null;
         transferForSignal(first);
         first = next;
     } while (first != null);
 }

部分内容参考zejian老师的博客, 博客地址

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值