AQS同步队列器之三:等待通知机制
一、简介
Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()、signal()这种方式实现线程间协作更加安全和高效。简单说,他的作用是使得某些线程一起等待某个条件(Condition),只有当该条件具备(signal 或者 signalAll方法被调用)时,这些等待线程才会被唤醒,从而重新争夺锁。wait()、notify()这些都更倾向于底层的实现开发,而Condition接口更倾向于代码实现的等待通知效果。两者之间的区别与共通点也可以了解一下:
对比项 | Object监视器 | Condition |
前置条件 | 获取对象的锁 |
调用Lock.lock()获取锁 Lock.newCondition获取Condition对象 |
调用方式 | 直接调用Object.notify() | 直接调用condition.await() |
等待队列的个数 | 一个 | 多个 |
当前线程释放锁进入等待状态 | 支持 | 支持 |
当前线程释放锁进入等待状态在等待状态中不断响应中断 | 不支持 | 支持 |
当前线程释放锁并进入等待超时状态 | 支持 | 支持 |
当前线程释放锁并进入等待状态直到将来的某个时间 | 不支持 | 支持 |
唤醒等待队列中的一个线程 | 支持notify() | 支持condition.signal() |
唤醒等待队列中的全部线程 | 支持notifyAll() | 支持condition.signalAll() |
相比之下Condition提供了比Object监视器更方便更全面的处理方式,而且使用起来也依旧很简单。
二、简单使用示例
1 import java.util.concurrent.locks.Condition; 2 import java.util.concurrent.locks.Lock; 3 import java.util.concurrent.locks.ReentrantLock; 4 5 public class LockTest { 6 //创建一个锁对象 8 private static Lock lock = new ReentrantLock(); 9 private static Condition condition_1 = lock.newCondition();//从锁对象中获取Condition 10 private static Condition condition_2 = lock.newCondition(); 43 private void lockwait(Condition condition) { 44 lock.lock();//先获取锁 45 try { 46 System.out.println( Thread.currentThread().getName() +" start wait......."); 47 condition.await();//让线程进入等待的状态 48 System.out.println( Thread.currentThread().getName() +" over wait......."); 49 } catch (InterruptedException e) { 50 e.printStackTrace(); 51 } finally { 52 lock.unlock();//释放锁 53 } 54 } 55 56 private void locksignal(Condition condition) { 57 lock.lock();//获取锁 58 try { 59 System.out.println(Thread.currentThread().getName() + " start notify...."); 60 condition.signal();//唤醒某个线程 61 } finally { 62 lock.unlock();//解锁 63 } 64 } 66 }
通过简单的示例,使用Condition具备两个条件,首先线程一定需要获取到当前的同步状态,其次必须从锁中获取到Condition对象,而condition.await()方法就对应了Object.wait()方法使得当前线程在满足某种条件的时候就进行等待,condition.signal()就是在某种条件下唤醒当前线程。
三、Condition等待/通知机制的实现原理
首先可以看一下Condition接口的定义的相关方法:
await():使当前线程进入等待状态直到被signal()、signalAll()方法唤醒或者被中断
awaitUninterruptibly():使当前线程进入等待状态直到被signal()或者signalAll()方法唤醒和await()方法的区别在于中断以后也会等待只有调用唤醒方法时才会被唤醒
awaitNanos(long nanosTimeout):超时等待
await(long time,TimeUnit unit):使当前线程进入等待状态直到被唤醒或者中断或者超过多少时间
awaitUntil(Date deadline):使当前线程进入等待状态直到到了指定的时间或者被唤醒或者中断
signal():唤醒等待中的一个线程
signalAll():唤醒等待中的全部线程
Condition接口只是定义了相关的处理等待通知的方法,真正实现其等待通知效果的在AQS中的ConditionObject类,在了解源码之前先讲一下同步队列和等待队列:
前面的文章讲过当线程未获取到同步状态的时候,会创建一个Node节点并把这个节点放入同步队列的尾部,进入同步队列的中的线程都是阻塞的。同步队列是一个FIFO先进先出的双向队列,而等待队列又是啥呢?
在AQS中同步队列和等待队列都复用了Node这个节点类,一个同步状态可以含有多个等待队列,同时等待队列只是一个单向的队列
接下来可以看一些重点方法实现的源码了解一下原理
await():使当前线程进入等待状态
1 public final void await() throws InterruptedException { 2 if (Thread.interrupted())//响应中断用的抛出异常就会被捕获中断 3 throw new InterruptedException(); 4 Node node = addConditionWaiter();//放入到等待队列中 5 int savedState = fullyRelease(node);//释放同步状态(同步队列头节点释放状态唤醒后继节点获取同步状态) 6 int interruptMode = 0;
//判断是否在同步队列中 7 while (!isOnSyncQueue(node)) { 8 LockSupport.park(this);//阻塞该线程 9 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//判断等待过程中是否被中断过 10 break; 11 }
//自旋去获取同步状态【在AQS中了解】获取成功并且在退出等待时不抛出中断异常(抛出了异常就会立马被中断) 12 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 13 interruptMode = REINTERRUPT;//在退出等待时重新中断 14 if (node.nextWaiter != null) //节点的后继等待节点不为null 15 unlinkCancelledWaiters();//移除所有不是等待状态的节点 16 if (interruptMode != 0) 17 reportInterruptAfterWait(interruptMode);//如果在等待过程中发现被中断,就执行中断的操作 18 }
addConditionWaiter():往等待队列中添加元素
1 private Node addConditionWaiter() { 2 Node t = lastWaiter;//等待队列中的最后一个元素 4 if (t != null && t.waitStatus != Node.CONDITION) {//如果尾节点部位null,并且尾节点不是等待状态中 5 unlinkCancelledWaiters();//从等待队列中移除 6 t = lastWaiter; 7 } 8 Node node = new Node(Thread.currentThread(), Node.CONDITION);//创建一个等待状态的节点 9 if (t == null) 10 firstWaiter = node; 11 else 12 t.nextWaiter = node; 13 lastWaiter = node;//加入等待队列的尾部 14 return node; 15 }
unlinkCancelledWaiters():将不是等待状态的节点从等待队列中移除
1 private void unlinkCancelledWaiters() { 2 Node t = firstWaiter;//头节点 3 Node trail = null; 4 while (t != null) { 5 Node next = t.nextWaiter;//下一个节点 6 if (t.waitStatus != Node.CONDITION) {//等待状态不为-2,代表不是等待状态 7 t.nextWaiter = null;//t的指向的下一个引用为null 8 if (trail == null) 9 firstWaiter = next;//赋值顶替 10 else 11 trail.nextWaiter = next; 12 if (next == null) 13 lastWaiter = trail; 14 } 15 else 16 trail = t;//将t赋值给trail 17 t = next;//next赋值给t 18 } 19 }
fullyRelease(Node node):释放当前状态值,返回同步状态
1 final int fullyRelease(Node node) { 2 boolean failed = true;//失败状态 3 try { 4 int savedState = getState();//获取当前同步状态值 5 if (release(savedState)) {//独占模式下释放同步状态,AQS独占式释放锁、前面文章讲过 6 failed = false;//失败状态为false 7 return savedState;//返回同步状态 8 } else { 9 throw new IllegalMonitorStateException(); 10 } 11 } finally { 12 if (failed) 13 node.waitStatus = Node.CANCELLED;//取消等待状态 14 } 15 }
总结一下等待操作:
首先等待操作没有进行CAS或者任何的同步操作,因为调用await()方法的是获取当前lock锁对象的线程,也就是同步队列中的首节点,当调用await()方法后,将同步队列的首节点创建一个等待节点放入等待队列的尾部,然后释放出同步状态(不释放同步状态就会造成死锁),唤醒同步队列中的后继节点,然后当前线程进入等待的状态
signal():唤醒等待队列中的一个线程
public final void signal() { if (!isHeldExclusively())//判断当前线程是否已经获取同步状态 throw new IllegalMonitorStateException(); Node first = firstWaiter;//等待队列头节点 if (first != null) doSignal(first);//具体实现方法唤醒第一个node }
doSignal(Node node):具体处理唤醒节点的操作
private void doSignal(Node first) { do { if((firstWaiter = first.nextWaiter) == null)//执行移除头节点的操作 lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); }
transferForSignal(Node node):唤醒的具体实现方式
final boolean transferForSignal(Node node) { if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//将节点的等待状态设置更改为初始状态如果改变失败就会被取消 return false; Node p = enq(node);//往同步队列中添加节点 int ws = p.waitStatus;//获取节点的等待状态 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//如果该结点的状态为cancel 或者修改waitStatus失败,则直接唤醒 LockSupport.unpark(node.thread);//具体的唤醒操作 return true; }
总结一下唤醒操作的流程:当调用signal()方法时,将等待队列中的首节点拿出来,加入到同步队列中,此时该节点不会立刻被唤醒因为就算被唤醒也是需要重新去获取同步状态的,而是在调用lock.unlock()方法释放锁以后将其唤醒获取同步状态。
到现在为止,基本的Condition的等待通知机制已经讲解完毕,至于附加功能的比如超时等待或者唤醒全部的功能在源码上都差不了多少稍微新增一些功能需要。
==================================================================================
不管岁月里经历多少辛酸和艰难,告诉自己风雨本身就是一种内涵,努力的面对,不过就是一场命运的漂流,既然在路上,那么目的地必然也就是前方。
==================================================================================
上一篇: java-并发编程
下一篇: php intval