怎么在Java多线程中使用wait、notify、notifyAll

本篇文章给大家分享的是有关怎么在Java多线程中使用wait、notify、notifyAll,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。

基础知识

怎么在Java多线程中使用wait、notify、notifyAll

首先我们需要知道,这几个都是Object对象的方法。换言之,Java中所有的对象都有这些方法。

publicfinalnativevoidnotify();
publicfinalnativevoidnotifyAll();
publicfinalnativevoidwait(longtimeout)throwsInterruptedException;
publicfinalvoidwait()throwsInterruptedException{
wait(0);
}

其中notify()、notifyAll()、wait(long timeout)是本地方法

其次我们需要知道这几个方法主要是用来个线程之间通信的。那可能就有人会问,既然是用来线程之间通信的,那为什么这几个方法不是在线程类Thread上呢?对于这个问题,我们先来看下这几个方法的具体使用方式再来回答这个问题。

用法

Java中规定,在调用者三个方法时,当前线程必须获得对象锁。因此就得配合synchronized关键字来使用

//使用模式,不代表可运行代码
synchronized(object){
while(contidion){
object.wait();
}
//object.notify();
//object.notifyAll();
}

或者

publicsynchronizedvoidmethodName(){
while(contidion){
object.wait();
}
//object.notify();
//object.notifyAll();
}

在synchronized拿到对象锁之后,synchronized代码块或者方法中,必定是会持有对象锁的,因此就可以使用wait()或者notify()。

通过上述使用方法,我们也能很好理解为什么这几个方法是在Object上而不是在Thread上。因为每个对象都可以作为synchronized锁的对象,因此wait、notify等必须和对象关联才能配合synchronized使用。

作用

方法作用
wait线程自动释放占有的对象锁,并等待notify。
notify随机唤醒一个正在wait当前对象的线程,并让被唤醒的线程拿到对象锁
notifyAll唤醒所有正在wait当前对象的线程,但是被唤醒的线程会再次去竞争对象锁。因为一次只有一个线程能拿到锁,所有其他没有拿到锁的线程会被阻塞。推荐使用。

1、wait()、notify/notifyAll() 方法是Object的本地final方法,无法被重写。

2、wait()使当前线程阻塞,前提是 必须先获得锁,一般配合synchronized 关键字使用,即,一般在synchronized 同步代码块里使用 wait()、notify/notifyAll() 方法。

3、 由于 wait()、notify/notifyAll() 在synchronized 代码块执行,说明当前线程一定是获取了锁的。

当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。

只有当 notify/notifyAll() 被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。

也就是说,notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况。所以在编程中,尽量在使用了notify/notifyAll() 后立即退出临界区,以唤醒其他线程

4、wait() 需要被try catch包围,中断也可以使wait等待的线程唤醒。

5、notify 和wait 的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

6、notify 和 notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

7、在多线程中要测试某个条件的变化,使用if 还是while?

要注意,notify唤醒沉睡的线程后,线程会接着上次的执行继续往下执行。所以在进行条件判断时候,可以先把 wait 语句忽略不计来进行考虑,显然,要确保程序一定要执行,并且要保证程序直到满足一定的条件再执行,要使用while来执行,以确保条件满足和一定执行。如下代码:

publicclassK{
//状态锁
privateObjectlock;
//条件变量
privateintnow,need;
publicvoidproduce(intnum){
//同步
synchronized(lock){
//当前有的不满足需要,进行等待
while(now<need){
try{
//等待阻塞
wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println("我被唤醒了!");
}
//做其他的事情
}
}
}

显然,只有当前值满足需要值的时候,线程才可以往下执行,所以,必须使用while 循环阻塞。注意,wait() 当被唤醒时候,只是让while循环继续往下走.如果此处用if的话,意味着if继续往下走,会跳出if语句块。但是,notifyAll 只是负责唤醒线程,并不保证条件云云,所以需要手动来保证程序的逻辑。

实际案例

接下来我们就使用wait()、notify()来实现一个生产者、消费者模式。这个也是面试过程中可能会被问到的地方。至于什么是生产者消费者模式,不明白的同学请自行百度。

首先是一些基础的代码

privatestaticBooleanrun=true;//控制是否生产和消费
privatestaticfinalIntegerMAX_CAPACITY=5;//缓冲区最大数量
privatestaticfinalLinkedBlockingQueue<String>queue=newLinkedBlockingQueue<>();//缓冲队列

生产者代码

/**
*生产者
*/
classProducterextendsThread{
@Override
publicvoidrun(){
while(run){
synchronized(queue){
while(queue.size()>=MAX_CAPACITY*2){
try{
System.out.println("缓冲队列已满,等待消费");
queue.wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}

try{
Stringstring=UUID.randomUUID().toString();
queue.put(string);
System.out.println("生产:"+string);
Thread.sleep(500);
}catch(InterruptedExceptione){
e.printStackTrace();
}

queue.notifyAll();//通知生产者和消费者
}
}
}
}

消费者代码

/**
*消费者
*/
classConsumerextendsThread{
@Override
publicvoidrun(){
while(run){
synchronized(queue){
while(queue.isEmpty()){
try{
System.out.println("队列为空,等待生产");
queue.wait();
}catch(InterruptedExceptione){
e.printStackTrace();
}
}

try{
System.out.println("消费:"+queue.take());
Thread.sleep(500);
}catch(InterruptedExceptione){
e.printStackTrace();
}

queue.notifyAll();//通知生产者和消费者
}
}
}
}

代码说明

1、生产者和消费者都继承了线程Thread,因为wait、notify本身就是线程间通信使用

2、生产者和消费者都有两层while,外层的while是用来判断是否运行生产者和消费者。内存的while用来判断队列queue是否已满或者为空,如果满足条件,则使得当前线程变成等待状态(等待notify)。

3、内层的条件判断为什么用while不用if,原因是当线程被wait之后,会释放对象锁。当等待的线程被notify之后,必须再次尝试去获取对象锁,如果没有获取到对象锁,那还必须等待,直到拿到对象锁之后才能向后执行。

4、当生产者生产了一个数据或者消费者消费了一个数据之后,使用notifyAll()方法来通知所有等待当前对象锁的线程,但是一次只会有一个等待的线程能拿到锁。

5、我们使用queue作为锁的对象在不同线程之间进行通信

代码运行结果

队列为空,等待生产生产:e422484e-8eb3-4a91-8a7c-97e21d7ef498生产:7894b802-2529-4798-ba98-9f0657f39240生产:848f6759-a427-4a94-89dc-3f484daaa467生产:f711d3dc-972c-4c44-8640-faffe376d354生产:38a08e62-d774-4ed5-8b51-f1ad534c246f消费:e422484e-8eb3-4a91-8a7c-97e21d7ef498消费:7894b802-2529-4798-ba98-9f0657f39240消费:848f6759-a427-4a94-89dc-3f484daaa467消费:f711d3dc-972c-4c44-8640-faffe376d354消费:38a08e62-d774-4ed5-8b51-f1ad534c246f队列为空,等待生产生产:9fae26f3-0b6e-4fbd-9620-040667efe0af生产:95bb1d88-e08a-4f70-a270-f75760994184消费:9fae26f3-0b6e-4fbd-9620-040667efe0af消费:95bb1d88-e08a-4f70-a270-f75760994184队列为空,等待生产生产:13d304bc-dff3-44a4-9527-2e0facd884e7生产:2693e069-bae1-4beb-adcd-a3c3bf5d232b消费:13d304bc-dff3-44a4-9527-2e0facd884e7消费:2693e069-bae1-4beb-adcd-a3c3bf5d232b

以上就是怎么在Java多线程中使用wait、notify、notifyAll,小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注恰卡编程网行业资讯频道。

发布于 2021-06-13 23:20:33
收藏
分享
海报
0 条评论
197
上一篇:怎么使用C语言解决八皇后问题 下一篇:C语言怎么判定一棵二叉树是否为二叉搜索树
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码