Condition使用总结

来源:互联网 发布:基于java博客 编辑:程序博客网 时间:2024/04/30 09:04

一、介绍

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 plaincopyprint?
  1. package com.meituan.hyt.test4;  
  2.   
  3. import java.util.concurrent.locks.Condition;  
  4. import java.util.concurrent.locks.ReentrantLock;  
  5.   
  6.   
  7. public class Main {  
  8.     public static void main(String[] args) {  
  9.         final ReentrantLock reentrantLock = new ReentrantLock();  
  10.         final Condition condition = reentrantLock.newCondition();  
  11.   
  12.         new Thread(new Runnable() {  
  13.             @Override  
  14.             public void run() {  
  15.                 reentrantLock.lock();  
  16.                 System.out.println(Thread.currentThread().getName() + "拿到锁了");  
  17.                 System.out.println(Thread.currentThread().getName() + "等待信号");  
  18.                 try {  
  19.                     condition.await();  
  20.                 } catch (InterruptedException e) {  
  21.                     e.printStackTrace();  
  22.                 }  
  23.   
  24.                 System.out.println(Thread.currentThread().getName() + "拿到信号");  
  25.   
  26.                 reentrantLock.unlock();  
  27.             }  
  28.         }, "线程1").start();  
  29.   
  30.         new Thread(new Runnable() {  
  31.             @Override  
  32.             public void run() {  
  33.                 reentrantLock.lock();  
  34.                 System.out.println(Thread.currentThread().getName() + "拿到锁了");  
  35.   
  36.                 try {  
  37.                     Thread.sleep(3000);  
  38.                 } catch (InterruptedException e) {  
  39.                     e.printStackTrace();  
  40.                 }  
  41.   
  42.                 System.out.println(Thread.currentThread().getName() + "发出信号");  
  43.                 condition.signalAll();  
  44.   
  45.                 reentrantLock.unlock();  
  46.             }  
  47.         }, "线程2").start();  
  48.     }  
  49. }  

运行结果:

线程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 plaincopyprint?
  1. public final void await() throws InterruptedException {  
  2.     // 1.如果当前线程被中断,则抛出中断异常  
  3.     if (Thread.interrupted())  
  4.         throw new InterruptedException();  
  5.     // 2.将节点加入到Condition队列中去,这里如果lastWaiter是cancel状态,那么会把它踢出Condition队列。  
  6.     Node node = addConditionWaiter();  
  7.     // 3.调用tryRelease,释放当前线程的锁  
  8.     long savedState = fullyRelease(node);  
  9.     int interruptMode = 0;  
  10.     // 4.为什么会有在AQS的等待队列的判断?  
  11.     // 解答:signal操作会将Node从Condition队列中拿出并且放入到等待队列中去,在不在AQS等待队列就看signal是否执行了  
  12.     // 如果不在AQS等待队列中,就park当前线程,如果在,就退出循环,这个时候如果被中断,那么就退出循环  
  13.     while (!isOnSyncQueue(node)) {  
  14.         LockSupport.park(this);  
  15.         if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)  
  16.             break;  
  17.     }  
  18.     // 5.这个时候线程已经被signal()或者signalAll()操作给唤醒了,退出了4中的while循环  
  19.     // 自旋等待尝试再次获取锁,调用acquireQueued方法  
  20.     if (acquireQueued(node, savedState) && interruptMode != THROW_IE)  
  21.         interruptMode = REINTERRUPT;  
  22.     if (node.nextWaiter != null)  
  23.         unlinkCancelledWaiters();  
  24.     if (interruptMode != 0)  
  25.         reportInterruptAfterWait(interruptMode);  
  26. }  
整个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)。 
0 0