Condition并发

xiaoxiao2021-02-28  103

一、介绍

Condition是在Java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。简单说,他的作用是使得某些线程一起等待某个条件(Condition),只有当该条件具备(signal 或者 signalAll方法被调用)时,这些等待线程才会被唤醒,从而重新争夺锁。

二、使用

Condition是个接口,基本的方法就是await()和signal()方法;

Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()  调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用 Conditon中的await()对应Object的wait(); Condition中的signal()对应Object的notify(); Condition中的signalAll()对应Object的notifyAll()。

三、示例代码

[java]  view plain  copy  print ? package com.meituan.hyt.test4;      import java.util.concurrent.locks.Condition;   import java.util.concurrent.locks.ReentrantLock;         public class Main {       public static void main(String[] args) {           final ReentrantLock reentrantLock = new ReentrantLock();           final Condition condition = reentrantLock.newCondition();              new Thread(new Runnable() {               @Override               public void run() {                   reentrantLock.lock();                   System.out.println(Thread.currentThread().getName() + "拿到锁了");                   System.out.println(Thread.currentThread().getName() + "等待信号");                   try {                       condition.await();                   } catch (InterruptedException e) {                       e.printStackTrace();                   }                      System.out.println(Thread.currentThread().getName() + "拿到信号");                      reentrantLock.unlock();               }           }, "线程1").start();              new Thread(new Runnable() {               @Override               public void run() {                   reentrantLock.lock();                   System.out.println(Thread.currentThread().getName() + "拿到锁了");                      try {                       Thread.sleep(3000);                   } catch (InterruptedException e) {                       e.printStackTrace();                   }                      System.out.println(Thread.currentThread().getName() + "发出信号");                   condition.signalAll();                      reentrantLock.unlock();               }           }, "线程2").start();       }   }   运行结果:

线程1拿到锁了 线程1等待信号 线程2拿到锁了 线程2发出信号 线程1拿到信号

四、原理

我们知道Lock的本质是AQS,AQS自己维护的队列是当前等待资源的队列,AQS会在资源被释放后,依次唤醒队列中从前到后的所有节点,使他们对应的线程恢复执行,直到队列为空。而Condition自己也维护了一个队列,该队列的作用是维护一个等待signal信号的队列。但是,两个队列的作用不同的,事实上,每个线程也仅仅会同时存在以上两个队列中的一个,流程是这样的: 1. 线程1调用reentrantLock.lock时,尝试获取锁。如果成功,则返回,从AQS的队列中移除线程;否则阻塞,保持在AQS的等待队列中。 2. 线程1调用await方法被调用时,对应操作是被加入到Condition的等待队列中,等待signal信号;同时释放锁。 3. 锁被释放后,会唤醒AQS队列中的头结点,所以线程2会获取到锁。 4. 线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。注意,这个时候,线程1 并没有被唤醒,只是被加入AQS等待队列。 5. signal方法执行完毕,线程2调用unLock()方法,释放锁。这个时候因为AQS中只有线程1,于是,线程1被唤醒,线程1恢复执行。 所以: 发送signal信号只是将Condition队列中的线程加到AQS的等待队列中。只有到发送signal信号的线程调用reentrantLock.unlock()释放锁后,这些线程才会被唤醒。 可以看到,整个协作过程是靠结点在AQS的等待队列和Condition的等待队列中来回移动实现的,Condition作为一个条件类,很好的自己维护了一个等待信号的队列,并在适时的时候将结点加入到AQS的等待队列中来实现的唤醒操作。

await源码:

[java]  view plain  copy  print ? public final void await() throws InterruptedException {       // 1.如果当前线程被中断,则抛出中断异常       if (Thread.interrupted())           throw new InterruptedException();       // 2.将节点加入到Condition队列中去,这里如果lastWaiter是cancel状态,那么会把它踢出Condition队列。       Node node = addConditionWaiter();       // 3.调用tryRelease,释放当前线程的锁       long savedState = fullyRelease(node);       int interruptMode = 0;       // 4.为什么会有在AQS的等待队列的判断?       // 解答:signal操作会将Node从Condition队列中拿出并且放入到等待队列中去,在不在AQS等待队列就看signal是否执行了       // 如果不在AQS等待队列中,就park当前线程,如果在,就退出循环,这个时候如果被中断,那么就退出循环       while (!isOnSyncQueue(node)) {           LockSupport.park(this);           if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)               break;       }       // 5.这个时候线程已经被signal()或者signalAll()操作给唤醒了,退出了4中的while循环       // 自旋等待尝试再次获取锁,调用acquireQueued方法       if (acquireQueued(node, savedState) && interruptMode != THROW_IE)           interruptMode = REINTERRUPT;       if (node.nextWaiter != null)           unlinkCancelledWaiters();       if (interruptMode != 0)           reportInterruptAfterWait(interruptMode);   }   整个await的过程如下:  

1.将当前线程加入Condition锁队列。特别说明的是,这里不同于AQS的队列,这里进入的是Condition的FIFO队列。 

2.释放锁。这里可以看到将锁释放了,否则别的线程就无法拿到锁而发生死锁。 

3.自旋(while)挂起,直到被唤醒(signal把他重新放回到AQS的等待队列)或者超时或者CACELLED等。 

4.获取锁(acquireQueued)。并将自己从Condition的FIFO队列中释放,表明自己不再需要锁(我已经拿到锁了)。

signal就是唤醒Condition队列中的第一个非CANCELLED节点线程,而signalAll就是唤醒所有非CANCELLED节点线程,本质是将节点从Condition队列中取出来一个还是所有节点放到AQS的等待队列。尽管所有Node可能都被唤醒,但是要知道的是仍然只有一个线程能够拿到锁,其它没有拿到锁的线程仍然需要自旋等待,就上上面提到的第4步(acquireQueued)。 
转载请注明原文地址: https://www.6miu.com/read-19199.html

最新回复(0)