Condition接口

任意一个java对象,都拥有一组监视器方法(定义在Object上),主要包括wait()、wait(long timeout)、notify()、notifyAll()方法,这些方法与Synchronized同步关键字配合使用,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。二者的区别如下:

对比项Object Monitor MethodsCondition
前置条件获取对象的锁调用Lock.lock()获取锁,调用Lock.newCondition()获取Condition对象
调用方式直接调用,object.wait()直接调用,Condition.wait()
等待队列个数一个多个
当前线程释放锁并进入等待状态支持支持
当前线程释放锁并进入等待状态,在等待状态中不响应中断不支持支持
当前线程释放锁并进入超时等待支持支持
当前线程释放锁并进入指定时间的等待状态不支持支持
唤醒等待队列中的一个线程支持支持
唤醒等待队列中的全部线程支持支持

Condition的实现分析

ConditionObject是同步器AbstractQueuedSynchronizer的内部类。每个Condition对象都包含着一个等待队列,该队列是Condition对象实现等待/通知功能的关键。

1.等待队列

等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会是释放锁、构造节点加入等待队列并进入等待状态。同步队列和等待队列中节点类型都是同步器的静态内部类AbstractQueuedSynchronizer.Node

一个Condition包含一个等待队列,Condition拥有一个首节点(firstWaiter)和一个尾节点(lastWaiter)。当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列,等待队列的基本结构如图所示:

如图所示,Condition用于首节点和尾节点的引用,而新增节点只需要将原有的尾节点的nextWaiter指向它,并且更新尾节点即可。节点引用更新操作的过程并没有使用到CAS,因为调用await方法的线程必定是获取了锁的线程。

在Object的监视器上,一个对象拥有一个同步队列和一个等待队列,但是并发包中的Lock拥有一个同步队列和多个等待队列。

Condition的实现是同步器的内部类,因此每个Condition实例都能访问同步器提供的方法,相当于每个Condition都拥有所属同步器的引用。

2.等待

如果从队列的角度看await方法,当调用await方法时,相当于把同步队列的首节点移到Condition的等待队列中。

        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();//当前线程加入到Condition的等待队列中
            int savedState = fullyRelease(node);//释放同步状态
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {//当前节点不在同步队列上等待重新获取同步状态
                LockSupport.park(this);//阻塞当前线程
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//判断是否在阻塞等待时被中断,返回0表示没有
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//加入到获取同步状态的竞争中
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) 
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

调用该方法 的线程成功获取了锁,也就是同步队列中的首节点,该方法会将当前线程构造成节点,并加入到等待队列中,然后是否同步状态,唤醒同步队列中的后继节点,然后当前线程进入等待状态。

当等待队列中的节点被唤醒,则唤醒节点的线程开始尝试获取同步状态。如果不是通过其他线程调用Condition.signal()方法唤醒,而是对等待线程进行中断,则会抛出InterruptedException

3. 通知

调用Condition的signal()方法,将会唤醒在等待队列中的首节点,在唤醒节点之前,会将节点移到同步队列中。

        public final void signal() {
            if (!isHeldExclusively())//判断当前线程是否获取了锁
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
    final boolean transferForSignal(Node node) {
         //如果不能改变waitStatus,则取消唤醒
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

    //移动到同步队列并尝试设置前驱节点的waitStatus以命令线程(可能)等待。 如果取消或尝试设置waitStatus失败,唤醒以重新同步
       Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);//唤醒当前等待队列中节点的线程
        return true;
    }

通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全地移到同步队列中。当节点移到到同步队列后,当前线程在使用LockSupport唤醒该节点的线程。

被唤醒后的线程,将从await()方法中的while循环中退出(isOnSyncQueue(Node node)方法返回true,节点已经在同步队列中),进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中。

成功获取同步状态之后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功地获取了锁

Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal方法,效果就是将等待队列中所有节点全部转移到同步队列中,并唤醒每个节点的线程。

好的,以下是一个简单的Java Condition接口使用示例: 假设有一个共享的缓冲区,其中生产者线程可以将数据项添加到缓冲区中,而消费者线程可以从中移除数据项。缓冲区有一个最大容量,当缓冲区已满时,生产者线程需要等待直到缓冲区中有空间可以添加数据。同样,当缓冲区为空时,消费者线程需要等待直到有数据项可供消费。 可以使用Java的Condition接口实现此类同步。以下是一个示例代码: ```java import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Buffer { private Queue<Integer> buffer; private int maxSize; private Lock lock; private Condition notFull; private Condition notEmpty; public Buffer(int maxSize) { this.maxSize = maxSize; buffer = new LinkedList<>(); lock = new ReentrantLock(); notFull = lock.newCondition(); notEmpty = lock.newCondition(); } public void put(int num) throws InterruptedException { lock.lock(); try { while (buffer.size() == maxSize) { notFull.await(); } buffer.offer(num); System.out.println("Produced " + num); notEmpty.signal(); } finally { lock.unlock(); } } public int take() throws InterruptedException { lock.lock(); try { while (buffer.size() == 0) { notEmpty.await(); } int num = buffer.poll(); System.out.println("Consumed " + num); notFull.signal(); return num; } finally { lock.unlock(); } } } ``` 在这个示例中,我们使用一个有限的缓冲区实现了一个简单的生产者-消费者模型。我们定义了一个缓冲区队列和一个最大容量。我们还使用了Java的Lock和Condition接口来确保线程之间的同步。 在构造函数中,我们初始化了缓冲区,锁和条件变量。我们使用ReentrantLock作为锁,使用newCondition()方法创建了两个条件变量:notFull和notEmpty。 在put()方法中,我们首先获取锁,然后使用while循环检查缓冲区是否已满。如果是,则调用notFull.await()方法将当前线程阻塞,直到缓冲区中有足够的空间可以添加新元素。如果缓冲区未满,则将新元素添加到缓冲区中,并通过notEmpty.signal()方法通知其他可能正在等待的消费者线程可以消费数据。最后,我们释放锁。 在take()方法中,我们首先获取锁,然后使用while循环检查缓冲区是否为空。如果是,则调用notEmpty.await()方法将当前线程阻塞,直到缓冲区中有可消费的元素。如果缓冲区不为空,则从缓冲区中取出一个元素,并通过notFull.signal()方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值