wait/notify与Condition简单分析

今天在翻《Effective Java》的时候偶然看到,并发工具优先与wait和notify,突然来了兴趣,探究下为什么这么说。

 

先了解下wait 和 notify的作用:

这两个方法的目的是为了避免自旋(或者说是等待)带来的性能损失。如果没有wait/notify,线程需要不停的轮询去查看某一条件是否达到了,这样就造成了CPU的浪费。而通过使用wait让线程挂起,等到条件运行完了再使用notify方法通知线程回复运行,这样就避免了CPU的浪费。

wait/notify使用的时候需要配合synchronized一起使用,因为这两个方法是Object类中的方法,说明是基于对象而存在的,可能有多个线程一起调用这两个方法。所以无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor),任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。

 

wait 和 notify的简单例子:

public class WNTest {

    private static final Object signal = new Object();

    public static void main(String[] args) {

        Thread thread1 = new Thread(new WaitThread());
        Thread thread2 = new Thread(new WaitThread());
        Thread thread3 = new Thread(new NotifyThread());

        thread1.start();
        thread2.start();
        thread3.start();



    }


    static class WaitThread implements Runnable{
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(1);
                synchronized (signal) {
                    System.out.println("Thread is in Wait ...");
                    signal.wait();
                }
                System.out.println("Thread is out Wait ...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    static class NotifyThread implements Runnable{

        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(5);
                synchronized (signal) {
                    System.out.println("Start Notify All Wait Thread ...");
                    signal.notifyAll();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

调用wait()方法时,它可以让线程暂时放弃对象锁,将锁交给其他线程使用。其他线程使用完成之后调用notify/notifyAll方法唤醒一个或者多个线程(具体唤醒哪一个线程由操作系统的线程调度机制来决定),继续执行后续的代码逻辑。

但是如果notify代码执行在wait之前,线程就永远唤醒不了。

 

为什么现在建议少使用wait和notify了:

wait/notify/notifyAll都是Java底层级别的方法,它是与对象对象监视器配合完成线程等待/通知机制。但是更高级别的Condition具有更高的可扩展性,它是与Lock配合完成线程的等待/通知机制,并且增加了很多功能特性:

  1. Condition支持不响应中断
  2. Condition可以支持多个等待队列,new 多个Condition即可(即多种等待条件,而wait只能支持一种)

(ps: 等待队列和同步队列是两种不同的队列,具体两者的区别详见“参考3”,后续回单独写一篇文章分析下AQS,之前其实也看过,看了半天没看明白...果然一口气吃不成胖子)

 

Condition方法说明:

Condition接口声明了如下几种方法:

这些方法的作用如下:

await()当前线程进入等待状态直到被signal或中断
awaitUninterruptibly()当前线程进入等待状态直到被通知,不响应中断。
awaitNanos(long nanosTimeout)当前线程进入等待状态直到被通知、中断或者超时,返回值表示剩余超时时间。
await(long time, TimeUnit unit)当前线程进入等待状态直到被通知、中断或者到某个时间。如果没有到指定时间就被通知。(方法和上面那个一样)
awaitUntil(Date deadline)当前线程进入等待状态直到被通知、中断或者到某个时间。如果没有到指定时间就被通知,方法返回true,否则,表示到了指定时间,返回false
signal()唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁。
signalAll()唤醒所有等待在Condition上的线程,能够从等待方法返回的线程必须获得与Condition相关联的锁。

和Object中的wait一样,其他线程调用await方法的时候会进入等待队列,只不过这个的等待队列在Condition对象内部,由Condition自身来维护。

 

 

 

 

 

 

 

 

 

自己写的一个Condition简单例子:

public class ConditionTest {
    private static final Lock LOCK = new ReentrantLock();
    private static final Condition condition1  = LOCK.newCondition();
    private static final Condition condition2  = LOCK.newCondition();

    public static void main(String[] args) {
        for (int i=0;i<3;i++){
            Thread thread = new Thread(() -> {
                LOCK.lock();
                try {
                    condition1.await();
                    System.out.println(Thread.currentThread().getName()+ " is out of wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    LOCK.unlock();
                }
            });
            thread.setName("condition1 Thread "+i);
            thread.start();
        }


        for (int i=0;i<3;i++){
            Thread thread = new Thread(() -> {
                LOCK.lock();
                try {
                    condition2.await();
                    System.out.println(Thread.currentThread().getName()+ " is out of wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    LOCK.unlock();
                }
            });
            thread.setName("condition2 Thread "+i);
            thread.start();
        }


        try {
            TimeUnit.SECONDS.sleep(3);
            LOCK.lock();
            condition1.signal();
            condition2.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            LOCK.unlock();
        }


    }

}

可以看出使用Condition可以同时实现了多种等待条件。调用await/signal方法和wait/notify一样,都要先获取到锁

 

Condition实现源码简单解析:

先看下await():

“4”之后表示要从await()方法中退出了,再来看下内部方法中的一些细节。

 

addConditionWaiter() -- 加入等待队列:

可以看出来是新建一个Node节点,然后在等待队列的尾部追加。

 

fullyRelease() -- 释放当前占有的锁:

acquireQuued()是AQS中的方法,后续分析AQS的时候再说把。总结就是调用await()方法,当前线程会释放锁,等待唤醒。

 

再来看下signal():

再来看下内部的doSignal()方法:

注意:这里的 Node p 可能是node's predecessor,或者直接是当前的node

简单来说,signal方法就是将线程从当前Condition的等待队列移动到AQS的同步队列,线程可能在AQS中等待唤醒/直接唤醒

 

 

AQS内部的一些细节后续单独写一篇博文详细分析吧,本文先简单分析ConditionObject中的一些方法。

 

 

参考:

https://blog.csdn.net/lengxiao1993/article/details/52296220(为什么wait和notify要放到同步块中)

https://blog.csdn.net/lv18092081172/article/details/79070122(wait/notify的一些理解)

https://blog.csdn.net/tb3039450/article/details/69056169(等待队列和同步队列的区别)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值