并发编程之ReentrantLock--Condition

介绍

Condition是条件锁的意思,是指在获取锁之后发现当前业务场景自己无法处理,而需要等待某个条件的出现才可以继续处理时使用的一种锁。
最常见的生产者消费者模式,生产的元素都被消费完,则需要阻塞消费者,不可继续消费直到生产者生产新的后唤醒消费者;或者生产者生产过快,容器已满,则需要阻塞生产者,直到消费者消费一定数量才唤醒生产者。

Condition是一个接口,其定义如下

public interface Condition {
	//阻塞当前线程,等待同一个Condition对象上的signal()/signalAll()唤醒
	//该方法响应中断,如果发生中断,该方法抛出InterruptedException异常
    void await() throws InterruptedException;
	///阻塞当前线程,等待同一个Condition对象上的signal()/signalAll()唤醒
	//与上面方法的区别是,该方法等待过程中不响应中断
    void awaitUninterruptibly();
	//阻塞线程,线程被阻塞指定的时间
	//当线程被中断、超时或者signal()/signalAll(),都会唤醒线程
    long awaitNanos(long nanosTimeout) throws InterruptedException;
	//同awaitNanos()
    boolean await(long time, TimeUnit unit) throws InterruptedException;
	//同awaitNanos()
    boolean awaitUntil(Date deadline) throws InterruptedException;
	//唤醒一个等待线程
    void signal();
	//唤醒所有的等待线程
    void signalAll();
}

AbstractQueuedSynchronizer提供了一个Condition的实现类ConditionObject,可以通过调用Lock.newCondition()获得一个Condition对象。每个Condition对象都与一个Lock对象相关,调用Condition对象的方法前必须获得对应Lock对象的锁。
Condition的作用与Object的wait()/notify()作用类似,调用await()可以阻塞当前线程,signal()/signalAll()可以唤醒其他阻塞线程。

基本使用

public class ConditionThread{
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(()->{
            try {
                lock.lock(); 
                try {
                    System.out.println("await阻塞前");  
                    // 等待条件
                    condition.await();  
                    System.out.println("await阻塞后");  
                } finally {
                    lock.unlock();
                    System.out.println("await阻塞线程解锁");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        //睡眠让await线程先拿到锁
        Thread.sleep(1000);
        lock.lock();  
        try {
            System.out.println("signal唤醒前");
            condition.signal();
            System.out.println("signal唤醒后");
        } finally {
            System.out.println("signal唤醒线程解锁");
            lock.unlock();  
        }
    }
}

运行结果:

await阻塞前
signal唤醒前
signal唤醒后
signal唤醒线程解锁
await阻塞后
await阻塞线程解锁

可以看出,上方线程先拿到锁,执行到await方法后阻塞了,下方线程拿到锁,执行signal,直到下方线程执行完unlock操作,上方线程被唤醒,继续后续操作。

源码解析

AQS中有两个队列,一个为AQS同步队列,未拿到锁的线程会放入此队列中;另一个是
Condition队列,调用await会放入到Condition队列。两个有几点不同,AQS同步队列是双向的,而Condition队列是单向的;AQS同步队列的首节点是虚拟节点,而Condition队列的首节点是真实节点,参与排队。
Condition队列如下图:(AQS同步队列结构可见前文)
在这里插入图片描述

await

 public final void await() throws InterruptedException {
        //线程中断,抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        //添加节点到Condition队列中,并返回该节点
        Node node = addConditionWaiter();
        // 完全释放当前线程获取的锁
        // 因为锁是可重入的,state >= 1,所以这里要把获取的锁全部释放
        int savedState = fullyRelease(node);
        int interruptMode = 0;
        //是否在同步队列中
        //注意这是aqs同步队列,区分Condition队列
        while (!isOnSyncQueue(node)) {
            // 阻塞当前线程
            LockSupport.park(this);
            // 上面部分是调用await()时释放自己占有的锁,并阻塞自己等待条件的出现
            // *************************分界线*************************  //
            // 下面部分是条件已经出现,尝试去获取锁
            //中断线程一系列操作
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        // 尝试获取锁
        // 如果没获取到会再次阻塞(前文对此方法有说明)
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        // 清除取消的节点
        if (node.nextWaiter != null)
            unlinkCancelledWaiters();
        // 线程中断相关
        if (interruptMode != 0)
        	//抛出InterruptedException,重新中断当前线程
        	//或不执行任何操作,具体取决于interruptMode值
            reportInterruptAfterWait(interruptMode);
    }

addConditionWaiter新建Node节点添加至Condition队列

private Node addConditionWaiter() {
        //获取条件队列的尾节点
        Node t = lastWaiter;
        // 如果条件队列的尾节点不为初始状态,从头节点开始清除所有不为初始状态节点
        if (t != null && t.waitStatus != Node.CONDITION) {
            unlinkCancelledWaiters();
            t = lastWaiter;
        }
        // 新建一个节点,它的等待状态是CONDITION
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        // 如果尾节点为空,则把新节点赋值给头节点(相当于初始化队列)
        // 否则把新节点赋值给尾节点的nextWaiter指针
        if (t == null)
            firstWaiter = node;
        else
            t.nextWaiter = node;
        // 尾节点指向新节点
        lastWaiter = node;
        // 返回新节点
        return node;
    }

fullyRelease 完全释放锁

 final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            //获取state,state >= 1,下面方法将state改为0表示释放锁
            int savedState = getState();
            //释放可重入锁,此方法上文有介绍
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            //失败将waitStatus设置为1,表示已取消的节点
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

isOnSyncQueue判断是否在AQS同步队列。
我们先说明下AQS同步队列和Condition队列中waitStatus等待状态的区别:
1、在Condition队列中,新建节点的初始等待状态是CONDITION(-2);
2、移到AQS的队列中时等待状态会更改为0(AQS同步队列节点的初始等待状态为0);
3、在AQS的队列中如果需要阻塞,会把它上一个节点的等待状态设置为SIGNAL(-1);
4、Condition队列和AQS队列中,已取消节点的等待状态设置为CANCELLED(1);
5、共享锁的时候还有另外一种等待状态叫PROPAGATE(-3)。

    final boolean isOnSyncQueue(Node node) {
        // 如果等待状态是CONDITION,或者前一个指针为空,返回false
        // 说明还没有移到AQS的队列中
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        // 如果next指针有值,说明已经移到AQS的队列中了
        if (node.next != null)
            return true;
        // 从AQS的尾节点开始往前寻找看是否可以找到当前节点
        // 找到了也说明已经在AQS的队列中了
        return findNodeFromTail(node);
    }

findNodeFromTail 从AQS的尾节点开始往前寻找看是否可以找到当前节点

  private boolean findNodeFromTail(Node node) {
        Node t = tail;
        //从tail尾节点往前循环
        //存在node节点返回true
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }

unlinkCancelledWaiters 清除取消的节点

 private void unlinkCancelledWaiters() {
 			//取Condition队列首节点
            Node t = firstWaiter;
            Node trail = null;
            //从首节点开始循环Condition队列
            while (t != null) {
                Node next = t.nextWaiter;
                //waitStatus 不为初始状态,则从Condition队列中清除
                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;
            }
        }

signal

 public final void signal() {
        // 判断持有锁的线程不为当前线程,抛出异常
        // 说明signal()要在获取锁之后执行
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        //取Condition队列首元素,执行doSignal操作
        Node first = firstWaiter;
        if (first != null)
            doSignal(first);
    }

doSignal执行唤醒操作,循环执行transferForSignal方法,直到transferForSignal方法返回true或执行到尾节点,意思从头开始循环,成功唤醒一位则退出循环

 private void doSignal(Node first) {
        do {
            // 移到条件队列的头节点往后一位
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            // 相当于把头节点从队列中出队
            first.nextWaiter = null;
            // 转移节点到AQS队列中
        } while (!transferForSignal(first) &&
                (first = firstWaiter) != null);
    }

transferForSignal将Node节点从Condition队列移动到AQS同步队列

    final boolean transferForSignal(Node node) {
        // 把节点的状态更改为0,也就是说即将移到AQS队列中
        // 如果失败了,说明节点已经被改成取消状态
        // 返回false,通过上面的循环可知会寻找下一个可用节点
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        // 调用AQS的入队方法把节点移到AQS的队列中
        // 注意,这里enq()的返回值是node的上一个节点,也就是旧尾节点
        Node p = enq(node);
        // 上一个节点的等待状态
        int ws = p.waitStatus;
        // 如果上一个节点已取消了,或者更新状态为SIGNAL失败(也是说明上一个节点已经取消了)
        // 则直接唤醒当前节点对应的线程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        // 如果更新上一个节点的等待状态为SIGNAL成功了
        // 则返回true,这时上面的循环不成立了,退出循环,也就是只通知了一个节点
        // 此时当前节点还是阻塞状态
        // 也就是说调用signal()的时候并不会真正唤醒一个节点
        // 只是把节点从条件队列移到AQS队列中
        return true;
    }

从上述源码可知,signal()方法并不会真正唤醒一个节点,只是把节点从Condition条件队列移到AQS队列中,一直等到unlock解锁后才会真正唤醒。

signalAll

 public final void signalAll() {
       // 判断持有锁的线程不为当前线程,抛出异常
       // 说明signal()要在获取锁之后执行
       if (!isHeldExclusively())
           throw new IllegalMonitorStateException();
       Node first = firstWaiter;
       if (first != null)
           doSignalAll(first);
   }

doSignalAll和doSignal很相似,doSignal从Condition队列头元素往后循环,成功转移一位元素则完成,而doSignalAll将所有符合条件的元素转移至AQS同步队列

  private void doSignalAll(Node first) {
      lastWaiter = firstWaiter = null;
      //从头节点开始循环转移,至到最后一个元素
      do {
          Node next = first.nextWaiter;
          first.nextWaiter = null;
          transferForSignal(first);
          first = next;
      } while (first != null);
  }

总结

await()方法的流程:
1、新建一个节点加入到条件队列中去;
2、完全释放当前线程占有的锁;
3、阻塞当前线程,并等待条件的出现;
8、条件已出现(此时节点已经移到AQS的队列中),尝试获取锁;

signal()方法的流程为:456
4、从条件队列的头节点开始寻找一个非取消状态的节点;
5、把它从条件队列移到AQS队列;
6、且只移动一个节点

7、signal线程unlock释放锁

需要注意一点:signal()方法后,最终会执行unlock()方法,此时才会真正唤醒一个节点,唤醒的这个节点如果曾经是条件节点的话又会继续执行await()方法LockSupport.park(this)下面的代码。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值