J.U.C--locks--Condition

来源:互联网 发布:网络高清摄像机哪家好 编辑:程序博客网 时间:2024/06/05 17:29

首先来解释一下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();    }}

运行结果:
这里写图片描述

从运行结果我们来分析:
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的等待队列中来实现的唤醒操作。

0 0