Thread类的wait(),notify(),notifyAll()接口的使用方法实战后的理解

java多线程在学习的时候感觉就那么几个类,几个方法,搞了一个生产者消费者例子就去参加校园招聘了。但真正用到后发现学的时候细节没有照顾到,有些问题也没有想过,所以书到用时方恨少。

既然生产者消费者这个例子是课堂上以及部分公司的招聘题目,那么就还是用它举个例子吧,例子不在于多么高大上,能帮助真正吸收就可以。

举个例子,如果你的Java程序中有两个线程——即生产者和消费者,那么生产者可以通知消费者,让消费者开始消耗数据,因为队列缓 冲区中有内容待消费(不为空)。相应的,消费者可以通知生产者可以开始生成更多的数据,因为当它消耗掉某些数据后缓冲区不再为满。
 

所以在对一个方法,一个对象,或者一个消耗数据添加锁的时候,就要顾虑到是不是锁的同一个对象。如果.wait() 和 .notify()两个方法对应的对象或者消耗数据不是一个的话,就不抛出异常 java.lang.IllegalMonitorStateException

所以: java.lang.IllegalMonitorStateException 异常:

 

这个异常会在三种情况下抛出:
1>当前线程不含有当前对象的锁资源的时候,调用obj.wait()方法;
2>当前线程不含有当前对象的锁资源的时候,调用obj.notify()方法。
3>当前线程不含有当前对象的锁资源的时候,调用obj.notifyAll()方法。

那我们在避免出现上述异常的情况下,就需要对wait,notify方法的使用规则详细了解。

 

如何使用Wait

尽管关于wait和notify的概念很基础,它们也都是Object类的函数,但用它们来写代码却并不简单。

例如在错误的地方使用 synchronized 关键词,表现是不知道synchronized同步快怎么使用,同步那个对象呢。
没有用正确的对象调用wait,不知道对什么对象调用wait方法
或者没有遵循规范的代码方法。 关于wait方法是有一个规范的写法的
第一个问题就是,我们怎么在代码里使用wait()呢? 正确的方法是对在多线程间共享的那个Object来调用wait。在生产者消费者问题中,这 个共享的Object就是那个缓冲区队列。 
第二个问题是,既然我们应该在synchronized的函数或是对象里调用wait,那哪个对象应该被synchronized呢?答案是,那个 你希望上锁的对象就应该被synchronized,即那个在多个线程间被共享的对象。在生产者消费者问题中,应该被synchronized的就是那个 缓冲区队列。

现在你知道wait应该永远在被synchronized的同步块下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在 while循环,而不是if语句中调用wait。 
因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等 待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤 醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者 开始消耗数据。所以记住,永远在while循环而不是if块中使用wait

基于以上认知,下面这个是使用wait和notify函数的规范代码模板:


// The standard idiom for calling the wait method in Java
synchronized (sharedObject) {
while (condition) {
sharedObject.wait();
// (Releases lock, and reacquires on wakeup)
}
// do action based upon condition e.g. take or put into queue
}

在while循环里使用wait的目的,是在线程被唤醒的前后都持续检查条件是否被满足。生产者线程如果条件并未改变,那么这个线程并不能被唤醒,可以防止对缓冲区队列误操作。

这是用wait/notifyAll方法对一个列表操作的生产者和消费者模型。 
这个方法是对列表进行设置的方法。用一个生产者线程调用这个方法。
 

public void setValue() {
        try {
            synchronized (lock) {
                while (!ValueObject.value.equals("")) {
                    // 数据集不为空,那么生产者要休息
                    System.out.println("生产者 " + Thread.currentThread().getName() + " WAITING ***");
                    lock.wait();
                }
                System.out.println("生产者 " + Thread.currentThread().getName() + "RUNNABLE ");
                String value = System.currentTimeMillis() + "_" + System.nanoTime();
                ValueObject.value = value;
                lock.notifyAll();

            }
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

//这是一个获取列表数据的方法,由消费者线程调用。

public void getValue() {
        try {
            synchronized (lock) {
                while (ValueObject.value.equals("")) {
                    System.out.println("消费者" + Thread.currentThread().getName() + " WAITING**");
                    lock.wait();
                }
                System.out.println("消费者" + Thread.currentThread().getName() + " Runnable");
                ValueObject.value = "";
                lock.notifyAll();
            }
        } catch (Exception e) {
            // TODO: handle exception
        }

    }

本文重点: 
1. 你可以使用wait和notify函数来实现线程间通信。你可以用它们来实现多线程(>3)之间的通信。 
2. 永远在synchronized的函数或对象里使用wait、notify和notifyAll,不然Java虚拟机会生成 IllegalMonitorStateException。 
3. 永远在while循环里而不是if语句下使用wait。这样,会在线程暂停恢复后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知。 
4. 永远在多线程间共享的对象(在生产者消费者模型里即缓冲区队列)上使用wait。 
5. 基于前文提及的理由,更倾向用 notifyAll(),而不是 notify()。用notify()方法只是讲一个因为wait操作处于等待的线程唤醒。可能生产者只是唤醒生产者。而没有唤醒消费者。所以为了防止多线程下假死情况都是用notifyAll()方法
 

本文是根据个人实际开发工作中遇到的问题,结合网络搜索的资料整合而成,部分见解谨代表个人看法,如有问题,欢迎讨论。

参考文档:

代码示例参考博客

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值