Condition在java 它出现在1.5中,用来取代传统的Objectwait()、notify()与Object的wait()相比,实现线程间的合作、notify(),使用Condition的await()、signal()这样,线程间合作才能更安全、更高效。因此一般情况下比较推荐使用Condition,阻塞队列实际上使用Condition来模拟线程间合作。但在java中Condition接口原理及实现这是一个复杂的过程。
Java里 sychronized和Lock+Condtion 都属于管程模型,Condition 管程模型代表等待条件。Condition在Lock的基础上使用,在原Lock的基础上,我们可以更有针对性,更灵活地协调多种条件下的线程协调。
每个 Condition 对象包含一个等待队列。在同步器中,同步队列中的节点被重复使用。Condition 对象的 await 和 signal 操作是等待队列和同步队列的操作。
await 操作:将当前线程构建成节点并添加到等待队列中,然后释放同步状态,唤醒同步队列中的后继节点,最后进入等待状态。
signal 操作:唤醒在等待队列中等待时间最长的节点(第一节点),并在唤醒节点之前将节点移动到同步队列。唤醒后的节点尝试竞争锁(旋转)。
首先,让我们通过一个简单的例子来看Condition的使用方法:
public static void main(String[] args) {
final ReentrantLock reentrantLock = new ReentrantLock();
final Condition condition = reentrantLock.newCondition();
Thread thread = new Thread((Runnable) () -> {
try {
reentrantLock.lock();
System.out.println("我要等一个新信号" + this);
condition.await();///这里需要注意的是,这是await,而不是wait
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("得到一个信号!!" + this);
reentrantLock.unlock();
}, "waitthread1");
thread.start();
Thread thread1 = new Thread((Runnable) () -> {
reentrantLock.lock();
System.out.println("我拿到锁了");
try {
Thread.sleep(3000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
condition.signalAll();
System.out.println("我发出一个信号!!");
reentrantLock.unlock();
}, "signalThread");
thread1.start();
}
运行结果如下:
我要等一个新信号lock.ReentrantLockTest$1@a62fc3
我拿到锁了
我发出一个信号!!
得到一个信号!!
从上面的例子可以看出,Condition的执行方法是在线程1中调用await方法后,线程1释放锁,睡觉等待唤醒。线程2获得锁后,开始做事。完成后,调用Condition的signal方法唤醒线程1,线程1恢复执行。说明Condition是一种多线程间协调通信的工具,使某个或某个线程能够一起等待某个条件(Condition),只有具备了这个条件( signal 或者 当带调用signalalll方法时, ,这些等待线程会被唤醒,从而重新争夺锁。
接下来分析一下Condition接口的原理:
Conditionobject是同步器AbstractQuedsynchronizer的内部类。由于Condition的操作需要获得相关的锁,因此作为同步器的内部类也更为合理。每个Condition对象都包含一个队列,这是Condition对象实现等待/通知功能的关键。以下是对Condition实现的分析,主要包括:等待队列、等待和通知。
等待队列是一个FIFO队列在队列中的每个节点都包含一个线程引用,即在Condition对象上等待的线程。如果一个线程调用Condition.await()方法,线程将释放锁,构建节点加入等待队列并进入等待状态。一个Condition包含一个等待队列,Condition有第一个节点(firstWaiter)和尾节点(lastWaiter)。Condition当前线程调用.await()方法将用当前线程构建节点,并从尾部添加节点等待队列。
调用Conditionawait()方法(或从await开始)将使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。从await()方法返回时,当前线程必须获得与Condition相关的锁。如果从队列(同步队列和等待队列)的角度看await()方法,当调用await()方法时,相当于同步队列的第一个节点(获得锁的节点)移动到等待队列的Condition。
调用Conditionsignal()方法将唤醒在等待队列中等待时间最长的节点(第一节点),并在唤醒节点之前将节点移动到同步队列。
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
调用该方法的前提条件是当前线程必须获得锁,可见signal()方法检查了isheldexclusively(),即当前线程必须是锁定线程。然后获得等待队列的第一个节点,将其移动到同步队列,并使用locksupport唤醒节点中的线程节点从等待队列移动到同步队列。
通过调用同步器enq(Node node)方法,等待队列中的头节点线程安全移动到同步队列。当节点移动到同步队列时,当前线程用LockSuport唤醒节点的线程。被唤醒的线程将从await()方法的while循环中退出(isOnSyncQueue(Node node)方法返回true,节点已经在同步队列中),然后将同步器的acquireQued()方法调用到获得同步状态的竞争中。在成功获得同步状态(或锁定)后,唤醒的线程将从之前调用的await()方法返回,此时该线程已成功获得锁定。ConditionsignalAll()方法相当于对等待队列中的每个节点进行一次signal()方法,其效果是将等待队列中的所有节点移动到同步队列中,唤醒每个节点的线程。
我说了很多,但我还是没有说得很详细。读完这么多内容后我无法理解。消化消化需要一定的时间。其实Condition接口只是java众多接口中的沧海一粟,所以学好java还有很长的路要走。当然,如果你想了解更多关于java接口的知识,可以免费查看蛙课网。Java面向接口课程。