草庐IT

wait 和 notify

bit me 2023-04-08 原文

✨个人主页:bit me👇
✨当前专栏:Java EE初阶👇
✨每日一语:阅己,越己,悦己;自行,自省,自醒;无味,无谓,无畏。

目 录

⏰一. wait 和 notify 的引入

由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知. 但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.

wait 和 notify 相比于 join 能更好的控制线程之间的执行顺序

  • wait (等待):让当前线程进入等待状态
  • notify (通知/唤醒):唤醒对应的 wait 线程。(从阻塞状态唤醒到就绪状态)

注意: wait, notify都是 Object 类的方法.

  • 使用 o1.notify() 就可以唤醒调用 o1.wait 的线程!!
  • 使用 o2.notify() 是不可以唤醒调用 o1.wait 的线程!

 

⏲二. wait()方法和notify()方法

对于 wait 来说,内部的执行过程还有点小麻烦

  1. 释放锁

wait 一上来就得释放锁,,得在调用 wait 之前,先拿到锁,wait 必须要放到 synchronized 中使用,synchronized 加锁的对象必须是和调用 wait 方法的对象是同一个对象,还得和调用 notify 的对象是同一个对象!!!

  1. 等待通知

  2. 当通知到达之后,就会被唤醒,并且尝试重新获取锁

wait 结束等待的条件:

  • 其他线程调用该对象的 notify 方法.
  • wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
  • 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常

代码示例: 观察wait()方法使用

public class Demo17 {
    public static void main(String[] args) throws InterruptedException {
        Object object = new Object();
        synchronized (object) {
            System.out.println("wait 之前");
            object.wait();
            System.out.println("wait 之后");
        }
    }
}

notify 方法是唤醒等待的线程

  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

举例实现 wait 和 notify 的内部执行过程:创建两个线程 一个线程调用 wait,一个线程调用 notify

public class Demo18 {
//这个对象用来做锁对象
public static Object locker = new Object();

public static void main(String[] args) {
    //用来取等待
    Thread waitTask = new Thread(()->{
        synchronized (locker) {
            try {
                System.out.println("wait 开始");
                locker.wait();
                System.out.println("wait 结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    });
    waitTask.start();

    //创建一个用来通知 / 唤醒的线程
    Thread notifyTask = new Thread(()->{
       //让用户来控制,用户输入某个内容之后,再执行通知
        Scanner scanner = new Scanner(System.in);
        System.out.println("输入任意内容,开始通知:");
        //next 会阻塞,直到用户真正输入内容以后
        scanner.next();

        synchronized (locker){
            System.out.println("notify 开始");
            locker.notify();
            System.out.println("notify 结束");
        }
    });
    notifyTask.start();
}
}

执行过程及结果:

线程 1 需要先计算一个结果,线程 2 来使用这个结果,线程 2 就可以 wait ,线程 1 计算完结果之后,notify ,唤醒线程 2。

  • wait 和 notify 机制,还能有效避免 "线程饿死"。(有些情况下,调度器可能分配不均匀,导致有些线程反复占用 CPU,有些线程始终捞不着 CPU)

例如:

多个线程抢占一个锁,一个线程 A 抢到了,其他线程排队等待,但是抢到锁的线程 A 却无法完成任务,占着锁不放,导致其他锁也不能干活儿,于是这个线程 A 决定释放锁出来,在外面等待,给其他的锁一些机会,由于线程之间在系统里的调度是随机的,可能下次又是线程 A 进来了,于是多次情况下都是线程 A 占用了 CPU,其他线程都碰不到,导致线程饿死,旱的旱死,涝的涝死。

因此使用 wait 和 notify 就可以解决上述问题,让后面的多个线程拿到锁之后判定当前任务是否可以执行,如果能就干活,不能就进行 wait !wait 来等待合适的时候(条件满足)再继续执行 / 再参与竞争锁

拓展:

  • 线程进入了 WAITING 状态,则务必要其他线程来主动唤醒
  • 线程进入了 BLOCKED 状态,这个则是其他线程把锁释放之后,操作系统来负责唤醒
  • 线程进入了 TIMED_WAITING 状态,操作系统会计时,时间到了之后进行唤醒

这三个阻塞的状态,都会导致 PCB 进入内核队列中对应的阻塞队列。操作系统内核是不区分这三个的状态的!这三个状态是 JVM 中的状态,操作系统内核中衡量线程的状态是另外的方式,没有 JVM 里面的这么细。

notify方法只是一次唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.

 

⏱三. wait 和 sleep 的对比(面试题)

其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间

他们俩都会让线程进入阻塞,阻塞的原因和目的不同,进入的状态也不同,被唤醒的条件也不一样。

实际开发中会很少使用 sleep ,目的是为了 “放权” ,暂时让出当前 CPU 的使用权。之所以 sleep 使用的少,等待的时间太固定了,要是有突发情况想提前唤醒并不是那么容易,wait 就用的比较多了,wait 可以进行死等,也能设置最长等待时间(涵盖了 sleep 的功能)

1. wait 需要搭配 synchronized 使用. sleep 不需要.
2. wait 是 Object 的方法 sleep 是 Thread 的静态方法.

有关wait 和 notify的更多相关文章

  1. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  2. ruby - 是否可以在不实际发送或读取数据的情况下查明 ruby​​ 套接字是否处于 ESTABLISHED 或 CLOSE_WAIT 状态? - 2

    s=Socket.new(Socket::AF_INET,Socket::SOCK_STREAM,0)s.connect(Socket.pack_sockaddr_in('port','hostname'))ssl=OpenSSL::SSL::SSLSocket.new(s,sslcert)ssl.connect从这里开始,如果ssl连接和底层套接字仍然是ESTABLISHED,或者它是否在默认值7200之后进入CLOSE_WAIT,我想检查一个线程几秒钟甚至更糟的是在实际上不需要.write()或.read()的情况下关闭。是用select()、IO.select()还是其他方法完成

  3. ruby - 如果运行了 tls_start,EventMachine 会在 close_connection 上发出 close_notify 吗? - 2

    我正在尝试编写一个基于EventMachine的简单FTPS服务器。控制socket工作正常。当使用数据套接字进行数据传输时,似乎一切正常(TLS握手成功完成并接收到数据),但随后我收到一条错误消息。这是我在Filezilla中得到的:Command:LISTResponse:150OpeningASCIImodedataconnectionforfilelistTrace:CFtpControlSocket::TransferParseResponse()Trace:code=1Trace:state=4Trace:CFtpControlSocket::SendNextCommand

  4. ruby - 如何使用 Watir::Waiter::wait_until 强制 Chrome 等待? - 2

    我试图告诉我的watir脚本等待注入(inject)ajax的登录框打开。我正在使用watir-webdriver,并在Chrome中进行测试。我无法让wait_until工作,如以下(简化)脚本中所述。require"rubygems"require"watir-webdriver"b=Watir::Browser.new(:chrome)site="www.example.com"b.gotositeputs"ClickonSignInbutton"b.link(:id,'btnLogin').clickputs"Waitingfortheusername/passworddial

  5. javascript - Chrome扩展: wait for storage.同步获取远程数据 - 2

    我有一个扩展程序可以读取带有时间戳的消息提要并提醒用户注意它们。我跟踪自用户打开消息以来的最高时间戳,低于该时间戳的任何内容均被“已读”。它运作良好,但当用户安装了多个Chrome时,问题就来了:他看到的是他已经在另一台机器上阅读过的项目。输入chrome.storage.syncAPI.我可以设置最高时间戳以在实例之间同步,但这会给我带来竞争条件。如果我在浏览器启动或从sleep中唤醒后立即使用chrome.storage.sync.get,它将获取陈旧的本地数据并且不会等待同步发生-经测试真的。因此,用户仍然会收到警报,即使在警报被清除之前的一小段时间内也是如此-这令人困惑。我可以

  6. javascript - Restangular:WAITING promise 的解决? - 2

    我是Javascript和AngularJS的新手,这个让我摸不着头脑:/先决条件从后端提供我的数据的REST服务AngularJS1.2.21和Restangular1.4.0一个AngularJSController,它将向服务请求所提供的增强版本我有什么这是有问题的方法:service.getSlices=function(){Restangular.all('entries').getList().then(function(entries){//somerathercomplexmodificationofthebackenddatagohere//...returnresu

  7. javascript - 等待模式 'please wait' 关闭 - 2

    等待“请稍候”模式关闭的标准Cypress方式是什么?很容易检查它在那里,但不使用cy.wait(ms),我怎样才能使Cypress持续检查一段时间,以查看该元素是否已从中删除DOM,还是不可见? 最佳答案 您可以简单地使用should断言它不存在,Cypress将等待该元素,直到它从DOM中删除。如果需要覆盖默认的4s超时,可以将其传递给上一个命令:cy.get(selector,{timeout:10000}).should("not.exist"); 关于javascript-等待

  8. javascript - Angular 4 - "wait for operation"的正确方法是什么? - 2

    我遇到了一个简单的问题,它有一个hacky解决方案setTimeout(...,0)。看看这个简单的代码:@Component({selector:'my-app',template:`SpanToDetect`,})exportclassApp{Items:Array=newArray();fill(){this.Items=[1,2,3,4,5,6,7,8,9,10]this.analyzeDom();//thishastorunhere}analyzeDom(){alert($("div.mySpan").length)//"0"//BUTifIsetthishackytrick

  9. javascript - casper.js 中的 setInterval 和 this.wait - 2

    我需要在每次迭代之间做一个3次2秒的循环。我尝试了这3个选项:选项1varcasper=require('casper').create({verbose:false,logLevel:'debug'});casper.start("http://google.com");casper.on('remote.message',function(msg){this.echo('remotemessagecaught:'+msg);})casper.thenEvaluate(function(){varx=0;varintervalID=setInterval(function(){con

  10. javascript - browser.sleep() 和 browser.wait() 方法有什么区别? - 2

    面对Protractor的计时问题。有时我的Protractor测试用例会因网络或性能问题而失败。我已经解决了browser.sleep()的现有问题。后来知道了browser.wait()。它们之间有什么区别,哪个更适合解决网络或性能问题。 最佳答案 当涉及到处理时间问题时,放置一个“快速”的browser.sleep()并继续前进是很诱人和容易的。问题是,它总有一天会失败。对于设置什么sleep超时没有黄金/通用规则,因此,在某些时候,由于网络或性能或其他问题,页面加载或元素变得可见等可能需要更多时间。另外,大多数时间,你最终会

随机推荐