wait和notify方法详解

wait()

  • 使当前线程等待,直到另一个线程为此对象调用notify()方法或notifyAll()方法。换句话说,这个方法的行为就像它只是执行wait(0)一样(如果没有当前对象notify()唤醒就会一直等待)。
  • 1.当前线程必须拥有此对象的monitor监视器(锁)2.当前线程调用wait()方法,线程就会释放此锁的所有权,并等待3.直到另一个线程通过调用notify方法或notifyAll方法通知在该对象的监视器(锁)上等待的线程唤醒4.然后线程等待,直到它可以重新获得该对象的监视器(锁)的所有权然后继续执行(被唤醒之后还需等待直到获取锁才能继续执行)。
  • 与单参数版本一样,中断和假唤醒是可能的,并且此方法应始终在循环中使用:
 synchronized (obj) {
 				//while 当线程被唤醒的再去判断是否获取monitor
               while (<condition does not hold条件不满足>) 
                   obj.wait();
               ... // 当前行为的动作
           }
  • 此方法只能由作为此对象监视器所有者的线程调用。有关线程成为监视器所有者的方式的说明,请参阅notify方法。
    public final void wait() throws InterruptedException {
    	//等待时间为0
        wait(0);
    }

示例

/**
 * 在调用wait方法时,线程必须要持有被调用对象的锁,当调用wait方法后,线程就会释放该对象的锁(monitor).
 * 在调用Thread类的sleep方法时,线程是不会释放对象的锁的。
 */
public class MyTest1 {

    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        synchronized (o) {
            o.wait();
        }
    }
}

在jvm字节码当中synchronized方法体伴随着如下的内容 在字节码层面上monitor标识
在这里插入图片描述

本地方法 wait(long timeout)

    public final native void wait(long timeout) throws InterruptedException;

  • 以下四种情况会被唤醒

    1 Some other thread invokes the notify method for this object and
    thread T happens to be arbitrarily chosen as the thread to be
    awakened.
    另一个线程调用这个对象的notify方法,而thread T)(当前线程)恰好被任意选择为要唤醒的线程。

    2 Some other thread invokes the notifyAll method for this object.
    另一个线程为此对象调用notifyAll方法。

    3 Some other thread interrupts thread T.
    另一个线程中断当前线程T。

    4 The specified amount of real time has elapsed, more or less. If
    timeout is zero, however, then real time is not taken into
    consideration and the thread simply waits until notified.
    指定的实时时间已过,或多或少。但是,如果timeout为零,则不考虑实时性,线程只是等待通知。

  • 当上述4种情况发生:从该对象的等待集中删除线程T,并重新启用线程调度。然后,它以通常的方式与其他线程竞争对象上的同步权;一旦它获得了对象的控制权,它对该对象的所有同步声明都将恢复到以前的状态(当前线程wait被调用时的状态),也就是说,恢复到调用wait方法时的状态。然后,线程T从wait方法的调用返回。因此,从wait方法返回时,对象和线程T的同步状态与调用wait方法时的状态完全相同(线程从等待到唤醒行为状态都是不变的)

  • 线程也可以在没有被通知、中断或超时的情况下被唤醒,这就是所谓的虚假唤醒。虽然这种情况在实践中很少发生,但应用程序必须通过测试本应导致线程被唤醒的条件,并在条件不满足时继续等待,以防出现这种情况。换句话说,等待应该总是在循环中发生,就像下面这样:

  synchronized (obj) {
               while (<condition does not hold>)
                   obj.wait(timeout);
               ... // Perform action appropriate to condition
           }

注意,wait方法在将当前线程放入该对象的等待集合(WaitSet)中时,只解锁该对象;当线程等待时,当前线程可能同步的任何其他对象都保持锁定(当前对象可能存在其他对象锁,当前对象notify只解锁当前对象不会影响其他的同步)

  • 此方法只能由作为此对象监视器所有者的线程调用

本地方法 notify()

  • 唤醒正在该对象的监视器(锁)上等待的单个线程。如果有多个线程正在等待此对象,则选择其中一个线程被唤醒。这种选择是任意的,由实现的自由裁量权决定。线程通过调用了wait方法的所有等待对象中的一个监视器(锁)。
  • 在当前线程放弃对该对象的锁定之前,唤醒的线程将无法继续。被唤醒的线程将以通常的方式与任何其他线程竞争,这些线程可能正积极地在该对象上进行同步;例如,被唤醒的线程在成为下一个锁定此对象的线程时没有可靠的特权或劣势(意思就是说被唤醒的线程不会有特权优先级之分与其他线程是平等都有可能锁定这个对象)。
  • 此方法只能由作为该对象监视器(锁)所有者的线程调用。线程通过以下三种方式之一成为对象监视器的所有者:
    1.通过执行该对象的被标记synchronized的 实例方法。
    2.通过执行在对象上被标记synchronized的 同步语句体。
    3.对于Class类型的对象,通过执行该类的标记synchronized的静态方法。
  • 一次只能有一个线程拥有一个对象的监视器(锁)。

本地方法 notifyAll()

  • 唤醒在该对象监视器上等待的(waitSet)所有线程(所有请求改对象的线程)。线程通过调用wait方法之一等待对象的监视器。
  • 在当前线程释放此对象的锁之前,唤醒的线程将无法继续被唤醒的线程将以通常的方式与任何其他线程竞争,这些线程可能正积极地在该对象上进行同步;例如,被唤醒的线程在成为下一个锁定此对象的线程时没有可靠的特权或劣势。
  • 此方法只能由作为此对象监视器(锁)所有者的线程调用。有关线程成为监视器所有者的方式的说明,请参阅notify方法。

关于wait与notify和notifyAll方法的总结

  1. 当调用wait时,首先需要确保wait方法的线程已经持有了对象的锁(monitor)。
  2. 当调用wait后,该对象就会释放掉这个对象的锁,然后进入到等待状态(wait set)
  3. 当线程调用了wait后进入到等待状态时,它就可以等待其他线程调用相同对象的notify或notofyAll方法来使得自己被唤醒
  4. 一旦这个线程被该对象的其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争);只有当该线程获取到了这个对象的锁后, 线程才会继续往下执行
  5. 调用wait方法的代码片段需要放一个synchronized块或者是synchronized方法中,这样才可以确保线程在调用wait方法前已经获取到该对象的锁
  6. 当调用对象的notify方法时,它会随机唤醒该对象等待集合(wait set)中的任意一个线程,当某个线程被唤醒后,它就会与其他线程一同竞争对象的锁。
    7.当调用对象的notifyAll方法时,它就会唤醒该对象等待集合(wait set)中的所有线程,这些线程被唤醒后,又会开始竞争对象的锁
  7. 在某一时刻,只有唯一一个线程可以拥有对象的锁。

大致意思就是wait 释放当前线程对该对象的锁,当前线程等待,notify去告诉被wait的线程可以启动了,多个就随机选个跟其他线程公平竞争改对象的锁 notifyAll就是释放该对象锁等待集合的所有线程去跟其他线程公平竞争改对象的锁。

我们可以通过以下代码进行验证

示例

/**
 * 编写一个多线程程序,实现这样一个目标:
 * <p>
 * 1.存在一个对象,该对象有一个int类型的成员变量counter,改成员变量的初始值为0.
 * 2.创建两个线程,其中一个线程对该对象的成员变量counter增1,另一个线程对该对象的成员变量减一。
 * 3.输出该对象成员变量counter每次变化后的值,
 * 4.最终输出的结果应为:10101010101010.。。。
 */
public class MyObject {

    public static void main(String[] args) {

        Counter counter = new Counter();
        Thread thread1 = new Thread(new ThreadTest(counter));
        Thread thread2 = new Thread(new ThreadTest2(counter));
        thread1.start();
        thread2.start();
    }
}

class ThreadTest implements Runnable {
    Counter counter;

    public ThreadTest(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
            counter.saveCounter();//+1

        }
    }
}

class ThreadTest2 implements Runnable {
    Counter counter;

    public ThreadTest2(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            counter.delCounter();

        }
    }

}


class Counter {

    private int conter;

    public synchronized void saveCounter() {
        if (conter != 0) {
            try {
                wait();// 此时为1不应该加1 释放锁进行等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        conter++;
        System.out.println(conter);
        notify();//线程执行完通知其他该对象等待线程集某个线程开始干活
    }

    public synchronized void delCounter() {

        if (conter != 1) {
            try {
                wait();//此时为0不应该减1 释放锁进行等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        conter--;
        System.out.println(conter);
        notify();//线程执行完通知其他线程干活
    }
}

当多个线程时

package com.example.demo.com.concurrecy.concurrency1;

/**
 * 编写一个多线程程序,实现这样一个目标:
 * <p>
 * 1.存在一个对象,该对象有一个int类型的成员变量counter,改成员变量的初始值为0.
 * 2.创建两个线程,其中一个线程对该对象的成员变量counter增1,另一个线程对该对象的成员变量减一。
 * 3.输出该对象成员变量counter每次变化后的值,
 * 4.最终输出的结果应为:10101010101010.。。。
 *
 * wait和notify关键字都必须在 synchronized当中
 */
public class MyObject {

    public static void main(String[] args) {

        Counter counter = new Counter();
        Thread thread1 = new ThreadTest(counter);
        Thread thread2 = new ThreadTest2(counter);

        /*
            多个线程时结果就不正确
            由于多个线程
            例如 第一个+1的线程执行完 加完之后第二个+1线程又抢到了
            此时会进行等待此时+1的线程又抢到了 由于此时counter!=1 此时两个加1的线程都是等待
            此时-1的线程执行结束唤醒notify 此时只有两个+1的线程在等待
            所有随机唤醒了一个 此时+1的线程执行完 又随机的唤醒一个而此时被等待的只有+1的线程
            此时被唤醒 所以此时就会有2输出 其他结果同理
            所以此时
             if (conter != 0) {
                 wait();
                 ...
            }

            }
            就要改成循环判断
             while (conter != 0) {
                wait();
                。。。

        }

         */
        Thread thread11 = new ThreadTest(counter);
        Thread thread22 = new ThreadTest2(counter);
        thread1.start();
        thread2.start();
        thread11.start();
        thread22.start();
    }
}

class ThreadTest extends Thread {
    Counter counter;

    public ThreadTest(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {

        for (int i = 0; i < 20; i++) {
            try
            {
                Thread.sleep((long)(Math.random() * 1000));
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            counter.saveCounter();//+1

        }
    }
}

class ThreadTest2 extends Thread {
    Counter counter;

    public ThreadTest2(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try
            {
                Thread.sleep((long)(Math.random() * 1000));
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            counter.delCounter();

        }
    }

}

class Counter {

    private int conter;

    public synchronized void saveCounter() {
        while (conter != 0) {
            try {
                wait();// 此时为1不应该加1 释放锁进行等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        conter++;
        System.out.println(conter);

        notify();//线程执行完通知其他该对象等待线程集某个线程开始干活
    }

    public synchronized void delCounter() {

        while (conter == 0) {
            try {
                wait();//此时为0不应该减1 释放锁进行等待 多个线程执行时当该线程再次运行还是要和其他线程争抢锁使用while
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        conter--;
        System.out.println(conter);

        notify();//线程执行完通知其他线程干活
    }
}
  • 11
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值