首页 > 编程知识 正文

java的等待通知机制(java异步消息通知机制)

时间:2023-12-24 12:05:20 阅读:320102 作者:EPCC

本文目录一览:

如何在 Java 中正确使用 wait,notify 和 notifyAll

我们先来了解一下为什么要使用wait,notify

首先看一下以下代码:

synchronized(a){

        ...//1

synchronized(b){

}

}

synchronized(b){

... //2

synchronized(a){

}

}

假设现在有两个线程, t1 线程运行到了//1 的位置,而 t2 线程运行到了//2 的位置,接

下来会发生什么情况呢?

此时, a 对象的锁标记被 t1 线程获得,而 b 对象的锁标记被 t2 线程获得。对于 t1 线程

而言,为了进入对 b 加锁的同步代码块, t1 线程必须获得 b 对象的锁标记。由于 b 对象的锁标记被 t2 线程获得, t1 线程无法获得这个对象的锁标记,因此它会进入 b 对象的锁池,等待 b 对象锁标记的释放。而对于 t2 线程而言,由于要进入对 a 加锁的同步代码块,由于 a 对象的锁标记在 t1 线程手中,因此 t2 线程会进入 a 对象的锁池。

此时, t1 线程在等待 b 对象锁标记的释放,而 t2 线程在等待 a 对象锁标记的释放。由

于两边都无法获得所需的锁标记,因此两个线程都无法运行。这就是“死锁”问题。

在 Java 中,采用了 wait 和 notify 这两个方法,来解决死锁机制。

首先,在 Java 中,每一个对象都有两个方法: wait 和 notify 方法。这两个方法是定义

在 Object 类中的方法。对某个对象调用 wait()方法,表明让线程暂时释放该对象的锁标记。

例如,上面的代码就可以改成:

synchronized(a){

...//1

a.wait();

synchronized(b){

}

}

synchronized(b){

... //2

synchronized(a){

...

a.notify();

}

}

这样的代码改完之后,在//1 后面, t1 线程就会调用 a 对象的 wait 方法。此时, t1 线程

会暂时释放自己拥有的 a 对象的锁标记,而进入另外一个状态:等待状态。

要注意的是,如果要调用一个对象的 wait 方法,前提是线程已经获得这个对象的锁标

记。如果在没有获得对象锁标记的情况下调用 wait 方法,则会产生异常。

由于 a 对象的锁标记被释放,因此, t2 对象可以获得 a 对象的锁标记,从而进入对 a

加锁的同步代码块。在同步代码块的最后,调用 a.notify()方法。这个方法与 wait 方法相对应,是让一个线程从等待状态被唤醒。

那么 t2 线程唤醒 t1 线程之后, t1 线程处于什么状态呢?由于 t1 线程唤醒之后还要在

对 a 加锁的同步代码块中运行,而 t2 线程调用了 notify()方法之后,并没有立刻退出对 a 锁的同步代码块,因此此时 t1 线程并不能马上获得 a 对象的锁标记。因此,此时, t1 线程会在 a 对象的锁池中进行等待,以期待获得 a 对象的锁标记。也就是说,一个线程如果之前调用了 wait 方法,则必须要被另一个线程调用 notify()方法唤醒。唤醒之后,会进入锁池状态。线程状态转换图如下:

由于可能有多个线程先后调用 a 对象 wait 方法,因此在 a 对象等待状态中的线程可能

有多个。而调用 a.notify()方法,会从 a 对象等待状态中的多个线程里挑选一个线程进行唤醒。

与之对应的,有一个 notifyAll()方法, 调用 a.notifyAll() 会把 a 对象等待状态中的所有线程都唤醒。

下面结合一个实际的例子,来演示如何使用 wait 和 notify / notifyAll 方法。

我使用一个数组来模拟一个比较熟悉的数据结构:栈。代码如下所示:

class MyStack{

    private char[] data = new char[5];

    private int index = 0;

    public char pop(){

        index -- ;

        return data[index];

    }

    public void push(char ch){

        data[index] = ch;

        index++;

    }

    public void print(){

        for (int i=0; iindex; i++){

            System.out.print(data[i] + "t");

        }

        System.out.println();

    }

    public boolean isEmpty(){

        return index == 0;

    }

    public boolean isFull(){

        return index == 5;

    }

}

注意,我们为 MyStack 增加了两个方法,一个用来判断栈是否为空,一个用来判断栈是

否已经满了。

然后我们创建两个线程,一个线程每隔一段随机的时间,就会往栈中增加一个数据;另

一个线程每隔一段随机的时间,就会从栈中取走一个数据。为了保证 push 和 pop 的完整性,在线程中应当对 MyStack 对象加锁。

但是我们发现,入栈线程和出栈线程并不是在任何时候都可以工作的。当数组满了的时

候,入栈线程将不能工作;当数组空了的时候,出栈线程也不能工作。违反了上面的条件,我们将得到一个数组下标越界异常。

为此,我们可以用 wait/notify 机制。在入栈线程执行入栈操作时,如果发现数组已满,

则会调用 wait 方法,去等待。同样,出栈线程在执行出栈操作时,如果发现数组已空,同

样调用 wait 方法去等待。在入栈线程结束入栈工作之后,会调用 notifyAll 方法,释放那些正在等待的出栈线程(因为数组现在已经不是空的了,他们可以恢复工作了)。同样,当出栈线程结束出栈工作之后,也会调用 notifyAll 方法,释放正在等待的入栈线程。

一段生产者/消费者相关代码:

class Consumer extends Thread{

    private MyStack ms;

    public Consumer(MyStack ms) {

        this.ms = ms;

    }

    public void run(){

        while(true){

        //为了保证 push 和 pop 操作的完整性

        //必须加 synchronized

            synchronized(ms){

            //如果栈空间已满,则 wait()释放 ms 的锁标记

                while(ms.isEmpty()){

                    try {

                        ms.wait();

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

                char ch = ms.pop();

                System.out.println("Pop " + ch);

                ms.notifyAll();

            }

            //push 之后随机休眠一段时间

            try {

                sleep( (int)Math.abs(Math.random() * 100) );

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

}

//生产者

class Producer extends Thread{

    private MyStack ms;

    public Producer(MyStack ms) {

        this.ms = ms;

    }

    public void run(){

        while(true){

        //为了保证 push 和 pop 操作的完整性

        //必须加 synchronized

            synchronized(ms){

            //如果栈空间已满,则 wait()释放 ms 的锁标记

                while(ms.isFull()){

                    try {

                        ms.wait();

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                }

                ms.push('A');

                System.out.println("push A");

                ms.notifyAll();

            }

            //push 之后随机休眠一段时间

            try {

                sleep( (int)Math.abs(Math.random() * 200) );

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    }

}

//主方法中,启用生产者与消费者两个线程

public class TestWaitNotify {

    public static void main(String[] args) {

        MyStack ms = new MyStack();

        Thread t1 = new Producer(ms);

        Thread t2 = new Consumer(ms);

        t1.start();

        t2.start();

    }

}

部分代码纯手打,望采纳~

java同步中,为什么要wait,又notify谁?

对象锁与同步块或者实例同步方法相关系,但如果线程进入静态同步方法,就必须获得类锁。用锁只能达到这样的目的:使得一个任务不会干涉另一个任务的资源,保证在任何时刻都只有一个任务可以访问某个资源。但两个任务要协同作战,互相通信,要一起工作去解决某个问题,必须使他们友好握手、共商国事。这种机制靠Object的方法wait()和notify()来安全地实现。在Thread对象上调用wait()方法将释放线程所有的锁定,这种说法是错误的。Thread类对象也是对象也有wait()方法,它释放的只是它自己作为线程对象的锁,这在线程池的概念级上理解。 似乎理解起来,wait()是自己停止,等待被唤醒;notify()也是自己停止,通知别人。那么感觉没什么大的区别,不急,先仔细分析他们的来历。wait()通常线程要执行下去需要等待某个条件发生变化,但改变这个条件已经超出了当前方法的控制能力。通常,这种条件由另一个任务来改变。既然执行不下去,傻等,又改变不了现实,那还不如交出执行权,令当前线程挂起,同步资源解锁,使别的线程可以访问并修改共享资源,自己进行排队队列,等候别人的通知。经过测试,好象是先入后出的顺序被唤醒的。释放了锁意味着另一个任务可以获得这个锁,这一点至关重要,因为这些其他的方法再入处理通常会引起wait()感兴趣的变化。wait()和notify()必须包括在synchronized代码块中,等待中的线程必须由notify()方法显式地唤醒,否则它会永远地等待下去。很多人初级接触多线程时,会习惯把wait()和notify()放在run()方法里,一定要谨记,这两个方法属于某个对象,应在对象所在的类方法中定义它,然后run中去调用它。 这里不得不提下,在Object的wait方法是重载的。有三个方法,了解一下除无参之外的另一个方法wait(毫秒数 n); 这里毫秒数是指,如果没有notify通知的情况下,当前被wait线程,经过n毫秒之后依然可以回到可运行状态。如果参数为零,则不考虑实际时间,在获得通知前该线程将一直等待。wait(0, 0) 与 wait(0) 相同。 notify()唤醒正在队列中等待资源的优先级最高的线程。但它自己不马上退出资源,继续执行,等全部执行完了,退出,释放锁,这样才让wait()的线程进入。所以说在对象(当前线程具有其锁定)调用notify()方法一定释放锁定是只是一厢情愿的。至于与notifyAll()区别,后者更加安全。使用notify(),在众多等待同一个锁的任务中只有一个会被唤醒,因此如果你希望使用notify(),就必须保证被唤醒的是恰当的任务。notify()也就是this.notify(),唤醒所有争抢自己的线程,与别的对象产生的wait()没有关系。 synchronized (a) {

System.out.println("notify");

a.notifyAll(); //假如这里是wait(),下句代码就暂时不会执行!

System.out.println("continue"); //notifyAll()以后,这句代码还是要执行的

} 我曾经自己写过如下非常幼稚的代码,写在public void run()里边,目的是在zoneRectangleSize 1的情况下,使自己的线程处于阻塞状态,虽然不会出错,但这个wait调用的是线程类对象本身的wait(),毕竟它也是来自Object,所以肯定达不到预期的效果: synchronized (mainApp) {

} Thread的静态方法sleep()是不释放锁的,也不用操作锁,所以可以在非同步控制方法和run方法内部调用。也就是说当前线程即使进入sleep状态也抱着这把锁睡觉,保持高度的监控状态,即使其它线程在外边踢门叫嚷骂娘,他是心安理得,坚决不释放。如果一直昏睡下去,拥有同一对象资源的线程们都会玩完的。

java中notify怎么使用?

notify(),notifyAll()都是要唤醒正在等待的线程,前者明确唤醒一个,后者唤醒全部。

当程序不明确知道下一个要唤醒的线程时,需要采用notifyAll()唤醒所有在wait池中的线程,让它们竞争而获取资源的执行权,但使用notifyAll()时,会出现死锁的风险,因此,如果程序中明确知道下一个要唤醒的线程时,尽可能使用notify()而非notifyAll()。

java的等待唤醒机制必须要让线程等待吗

1. 线程的挂起和唤醒

挂起实际上是让线程进入“非可执行”状态下,在这个状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行;在线程挂起后,可以通过重新唤醒线程来使之恢复运行。

挂起的原因可能是如下几种情况:

(1)通过调用sleep()方法使线程进入休眠状态,线程在指定时间内不会运行。

(2)通过调用join()方法使线程挂起,使自己等待另一个线程的结果,直到另一个线程执行完毕为止。

(3)通过调用wait()方法使线程挂起,直到线程得到了notify()和notifyAll()消息,线程才会进入“可执行”状态。

(4)使用suspend挂起线程后,可以通过resume方法唤醒线程。

虽然suspend和resume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成死锁,因此,这两个方法被标识为deprecated(抗议)标记,这表明在以后的jdk版本中这两个方法可能被删除,所以尽量不要使用这两个方法来操作线程。

调用sleep()、yield()、suspend()的时候并没有被释放锁

调用wait()的时候释放当前对象的锁

wait()方法表示,放弃当前对资源的占有权,一直等到有线程通知,才会运行后面的代码。

notify()方法表示,当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复,然后继续运行wait()后面的语句。

notifyAll()方法表示,当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行。

2.等待和锁实现资源竞争

等待机制与锁机制是密切关联的,对于需要竞争的资源,首先用synchronized确保这段代码只能一个线程执行,可以再设置一个标志位condition判断该资源是否准备好,如果没有,则该线程释放锁,自己进入等待状态,直到接收到notify,程序从wait处继续向下执行。

synchronized(obj) {

while(!condition) {

obj.wait();

}

obj.doSomething();

}

以上程序表示只有一个线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A释放该锁,进入wait()。

在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:

synchronized(obj) {

condition = true;

obj.notify();

}

需要注意的是:

# 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) {...} 代码段内。

# 调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj) {...} 代码段内唤醒A。

# 当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。

# 如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。

# obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。

# 当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。