sleep()和wait()的区别及wait方法的一点注意事项

 

一.查看API

sleep是Thread类的方法,导致此线程暂停执行指定时间,给其他线程执行机会,但是依然保持着监控状态,过了指定时间会自动恢复,调用sleep方法不会释放锁对象。

当调用sleep方法后,当前线程进入阻塞状态。目的是让出CPU给其他线程运行的机会。但是由于sleep方法不会释放锁对象,所以在一个同步代码块中调用这个方法后,线程虽然休眠了,但其他线程无法访问它的锁对象。这是因为sleep方法拥有CPU的执行权,它可以自动醒来无需唤醒。而当sleep()结束指定休眠时间后,这个线程不一定立即执行,因为此时其他线程可能正在运行。

wait方法是Object类里的方法,当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时释放了锁对象,等待期间可以调用里面的同步方法,其他线程可以访问,等待时不拥有CPU的执行权,否则其他线程无法获取执行权。当一个线程执行了wait方法后,必须调用notify或者notifyAll方法才能唤醒,而且是随机唤醒,若是被其他线程抢到了CPU执行权,该线程会继续进入等待状态。由于锁对象可以时任意对象,所以wait方法必须定义在Object类中,因为Obeject类是所有类的基类。

二.是否可以传入参数

sleep()方法必须传入参数,参数就是休眠时间,时间到了就会自动醒来。

wait()方法可以传入参数也可以不传入参数,传入参数就是在参数结束的时间后开始等待,不穿如参数就是直接等待。

三.是否需要捕获异常

sleep方法必须要捕获异常,而wait方法不需要捕获异常。sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。

wait属于Object的成员方法,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt()方法而产生。

四.作用范围

wait、notify和notifyAll方法只能在同步方法或者同步代码块中使用,而sleep方法可以在任何地方使用。但是注意sleep是静态方法,也就是说它只对当前对象有效。通过对象名.sleep()想让该对象线程进入休眠是无效的,它只会让当前线程进入休眠。

五.调用者的区别

首先为什么wait、notify和notifyAll方法要和synchronized关键字一起使用?

因为wait方法是使一个线程进入等待状态,并且释放其所持有的锁对象,notify方法是通知等待该锁对象的线程重新获得锁对象,然而如果没有获得锁对象,wait方法和notify方法都是没有意义的,因此必须先获得锁对象再对锁对象进行进一步操作于是才要把wait方法和notify方法写到同步方法和同步代码块中了。

由此可知,wait和notify、notifyAll方法是由确定的对象即锁对象来调用的,锁对象就像一个传话的人,他对某个线程说停下来等待,然后对另一个线程说你可以执行了(实质上是被捕获了),这一过程是线程通信。sleep方法是让某个线程暂停运行一段时间,其控制范围是由当前线程决定,运行的主动权是由当前线程来控制(拥有CPU的执行权)。

其实两者的区别都是让线程暂停运行一段时间,但本质的区别一个是线程的运行状态控制,一个是线程间的通信。

六.wait方法(线程通信)的一些注意事项

例如以下代码:

当线程print1获得了锁对象(this)后进行判断,如果不满足条件(flag == 1)无法继续执行下面的代码,于是进入wait状态。在另一个线程print3中,由于改变了flag,使得线程print1的判断条件满足了,线程print1就会被唤醒。

class Printer {
private int flag = 1;
public void print1() throws Exception {
synchronized(this) {
while(flag != 1) {
this.wait();
}
System.out.print("1");
System.out.print("2");
System.out.print("3");
System.out.print("4");
System.out.println();
flag = 2;
this.notifyAll();
}
}
public void print2() throws Exception {
synchronized(this) {
while(flag != 2) {
this.wait();
}
System.out.print("5");
System.out.print("6");
System.out.print("7");
System.out.print("8");
System.out.println();
flag = 3;
this.notifyAll();
}
}
public void print3() throws Exception {
synchronized(this) {
while(flag != 3) {
this.wait();
}
System.out.print("A");
System.out.print("B");
System.out.print("C");
System.out.print("D");
System.out.println();
flag = 1;
this.notifyAll();
}
}
}

注意事项:

1.调用wait方法和notify、notifyAll方法前必须获得对象锁,也就是必须写在synchronized(锁对象){......}代码块中。

2.当线程print1调用了wait方法后就释放了对象锁,否则其他线程无法获得对象锁,也就无法唤醒线程print1。

3.当this.wait()方法返回后,线程必须再次获得对象锁后才能继续执行。

4.如果另外两个线程都在wait,则正在执行的线程调用notify方法只能唤醒一个正在wait的线程(公平竞争,由JVM决定)。

5.当使用notifyAll方法后,所有wait状态的线程都会被唤醒,但是只有一个线程能获得锁对象,必须执行完while(condition){this.wait();}后才能获得对象锁。其余的需要等待该获得对象锁的线程执行完释放对象锁后才能继续执行。

6.当某个线程调用notifyAll方法后,虽然其他线程被唤醒了,但是该线程依然持有着对象锁,必须等该同步代码块执行完(右大括号结束)后才算正式释放了锁对象,另外两个线程才有机会执行。

 wait()与sleep()的区别
sleep()方法是Thread类的静态方法,不涉及到线程间同步概念,仅仅为了让一个线程自身获得一段沉睡时间。sleep可以在任何地方使用。
wait()方法是object类的方法,解决的问题是线程间的同步,该过程包含了同步锁的获取和释放,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify()方法才会重新激活调用者。
注意:线程调用notify()之后,只有该线程完全从 synchronized代码里面执行完毕后,monitor才会被释放,被唤醒线程才可以真正得到执行权。

wait与notify是Java同步机制中重要的组成部分。结合与synchronized关键字使用,可以建立很多优秀的同步模型。

synchronized(this){}等价与public synchronized void method(){.....}

同步分为类级别和对象级别,分别对应着类锁和对象锁。类锁是每个类只有一个,如果static的方法被synchronized关键字修饰,则在这个方法被执行前必须获得类锁;对象锁类同。

首先,调用一个Object的wait与notify/notifyAll的时候,必须保证调用代码对该Object是同步的,也就是说必须在作用等同于synchronized(obj){......}的内部才能够去调用obj的wait与notify/notifyAll三个方法,否则就会报错:

java.lang.IllegalMonitorStateException: current thread not owner

在调用wait的时候,线程自动释放其占有的对象锁,同时不会去申请对象锁。当线程被唤醒的时候,它才再次获得了去获得对象锁的权利。

所以,notify与notifyAll没有太多的区别,只是notify仅唤醒一个线程并允许它去获得锁,notifyAll是唤醒所有等待这个对象的线程并允许它们去获得对象锁,只要是在synchronied块中的代码,没有对象锁是寸步难行的。其实唤醒一个线程就是重新允许这个线程去获得对象锁并向下运行。

顺便说一下notifyall,虽然是对每个wait的对象都调用一次notify,但是这个还是有顺序的,每个对象都保存这一个等待对象链,调用的顺序就是这个链的顺序。其实启动等待对象链中各个线程的也是一个线程,在具体应用的时候,需要注意一下。

随机推荐

  1. ABP中的模块初始化过程(一)

    在总结完整个ABP项目的结构之后,我们就来看一看ABP中这些主要的模块是按照怎样的顺序进行加载的,在加载的过程中我们会一步步分析源代码来进行解释,从而使自己对于整个框架有一个清晰的脉络,在整个Asp. ...

  2. JS判断浏览器是否支持触屏事件

    var hasTouch=function(){ var touchObj={}; touchObj.isSupportTouch = "ontouchend" in docume ...

  3. pestle.phar

    nstalll: 1,cd /usr/local/bin && curl -LO http://pestle.pulsestorm.net/pestle.phar : 2,chmod  ...

  4. 最大公约数和最小公倍数(Greatest Common Divisor and Least Common Multiple)

    定义: 最大公约数(英语:greatest common divisor,gcd).是数学词汇,指能够整除多个整数的最大正整数.而多个整数不能都为零.例如8和12的最大公因数为4. 最小公倍数是数论中 ...

  5. 用IntelliJ IDEA 开发Spring+SpringMVC+Mybatis框架 分步搭建三:配置spring并测试

    这一部分的主要目的是 配置spring-service.xml  也就是配置spring  并测试service层 是否配置成功 用IntelliJ IDEA 开发Spring+SpringMVC+M ...

  6. Security+认证812分轻松考过(备战分享)

    2019.02.12,开工第一天,我参加了security+考试并顺利通过了考试,812分的成绩有点出乎我的意料,据我所知我周围还没有人考过800分的.怀着愉悦的心态分享下我的备考经历和考试经验. 备 ...

  7. HZNU第十二届校赛赛后补题

    愉快的校赛翻皮水! 题解 A 温暖的签到,注意用gets #include <map> #include <set> #include <ctime> #inclu ...

  8. 使用Eclipse创建动态的web工程

    使用Eclipse创建动态的web工程 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.修改工作区的编码 1>.点击Window选择Preferences 2>.将默 ...

  9. 我对SAP Business One 项目实施的理解

    一.什么是SAP: 大家都知道ERP是什么,ERP是企业资源计划管理系统.是指建立在信息技术基础上,集信息技术与先进管理思想于一身,以系统化的管理思想,为企业员工及决策层提供决策手段的管理平台.那么问 ...

  10. 横向滚动布局 white-space:nowrap

    float + 两层DOM实现 html <div class="container"> <div class="div1 clearfix" ...