J.U.C--locks--Condition

首先来解释一下Condition有什么作用:Condition的作用和Java原生的通过synchronized与wait()和nitofy()/notifyAll()方法相结合实现等待/通知模型的作用是一样的。Condition是一个多线程间协调通信的工具类。

我觉得有个博客写得很好:http://ifeve.com/understand-condition/ 看着这个博客基本就能理解了。

我们都知道synchronized与wait()和nitofy()/notifyAll()方法相结合可以实现等待/通知模型,也可以实现生产者消费者模型。ReentrantLock同样可以,但是需要借助Condition,且Condition有更好的灵活性,具体体现在:
1)一个Lock里面可以创建多个Condition实例,实现多路通知。

2)notify()方法进行通知时,被通知的线程时Java虚拟机随机选择的,但是ReentrantLock结合Condition可以实现有选择性地通知,这是非常重要的。

1. Condition接口中的常用方法

1void await()
造成当前线程在接到信号或被中断之前一直处于等待状态。

2)boolean await(long time, TimeUnit unit)
造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。

3) long awaitNanos(long nanosTimeout)
造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。

4) void awaitUninterruptibly()
造成当前线程在接到信号之前一直处于等待状态。

5) boolean awaitUntil(Date deadline)
造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。

6) void signal() 唤醒一个等待线程。

7) void signalAll() 唤醒所有等待线程。

实例与解析

下面通过一个使用的实例来说明问题:

package thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionDemo {
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable(){

            @Override
            public void run() {
                lock.lock();
                System.out.println(Thread.currentThread().getName()+"正在运行。。。。");
                try {
                    Thread.sleep(2000);
                    System.out.println(Thread.currentThread().getName()+"停止运行,等待一个signal");
                    condition.await();//等待 并且会释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"获得一个signal,继续执行");
                lock.unlock();
            }

        },"waitThread");
        thread1.start();

        try {
            Thread.sleep(1000);//保证线程1先执行,否则线程1将一直等待signal信号
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        Thread thread2 = new Thread(new Runnable(){

            @Override
            public void run() {
                lock.lock();//获取锁
                System.out.println(Thread.currentThread().getName()+"正在运行。。。。");
                condition.signal();//发送信号,唤醒其它线程
                System.out.println(Thread.currentThread().getName()+"发送一个signal");
                System.out.println(Thread.currentThread().getName()+"发送一个signal后,结束");
                lock.unlock();//释放锁之后,thread1再执行
            }

        },"signalThread");
        thread2.start();

    }

}

运行结果:
J.U.C--locks--Condition_第1张图片

从运行结果我们来分析:
1)当thread1拿到锁之后开始执行,当调用condition.await()方法之后,thread1开始睡眠并释放锁。这里thread1会释放锁,这个是一定要知道的,其实Condition的内部也是AQS实现的,也是通过AQS维护的队列释放锁。注意在线程1和线程2之间加一个延时,保证线程1先获取锁。

2)thread1开始睡眠并释放锁之后,thread2拿到锁,拿到锁之后开始运行,并调用condition.signal()发射一个信号来唤醒正在等待此条件condition的线程。 (注意,这里线程2调用signal函数之后,线程1还没有被唤醒,必须等线程2释放锁之后,线程1才能够恢复,)。发射信号之后thread2会继续执行,执行完毕后thread2释放锁。

3)当thread2释放锁之后,thread1拿到锁开始继续运行直至结束。

上面的逻辑更加详细的来说;
我们知道AQS自己维护的队列是当前等待资源的队列,AQS会在资源被释放后,依次唤醒队列中从前到后的所有节点,使他们对应的线程恢复执行。直到队列为空。

而Condition自己也维护了一个队列,该队列的作用是维护一个等待signal信号的队列,两个队列的作用是不同,事实上,每个线程也仅仅会同时存在以上两个队列中的一个,流程是这样的:

  1. 线程1调用reentrantLock.lock时,线程被加入到AQS的等待队列中。

  2. 线程1调用await方法被调用时,该线程从AQS中移除,对应操作是锁的释放。

  3. 接着马上被加入到Condition的等待队列中,以为着该线程需要signal信号。

  4. 线程2,因为线程1释放锁的关系,被唤醒,并判断可以获取锁,于是线程2获取锁,并被加入到AQS的等待队列中。

  5. 线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。 注意,这个时候, 线程1 并没有被唤醒。

  6. signal方法执行完毕,线程2调用reentrantLock.unLock()方法,释放锁。这个时候因为AQS中只有线程1,于是,AQS释放锁后按从头到尾的顺序唤醒线程时,线程1被唤醒,于是线程1回复执行。

  7. 直到释放所整个过程执行完毕。

可以看到,整个协作过程是靠结点在AQS的等待队列和Condition的等待队列中来回移动实现的,Condition作为一个条件类,很好的自己维护了一个等待信号的队列,并在适时的时候将结点加入到AQS的等待队列中来实现的唤醒操作。

你可能感兴趣的:(Java多线程与并发)