通过两个小栗子来说说Java的sleep、wait、notify、notifyAll的用法
线程是计算程序运行的最小载体,由于单个单核CPU的硬件水平发展到了一定的瓶颈期,因此就出现了多核多CPU的情况,直接就导致程序员多线程编程的复杂。由此可见线程对于高性能开发的重要性。
那么线程在计算机中有好几种状态,他们之间是怎么切换的?sleep和wait又有什么区别?notify和notifyAll怎么用?带着这些问题,我们来看看Java的线程吧!
Thread的状态
先来看看Thread类里面都有哪几种状态,在Thread.class中可以找到这个枚举,它定义了线程的相关状态:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
如下图所示:

- NEW 新建状态,线程创建且没有执行start方法时的状态
- RUNNABLE 可运行状态,线程已经启动,但是等待相应的资源(比如IO或者时间片切换)才能开始执行
- BLOCKED 阻塞状态,当遇到synchronized或者lock且没有取得相应的锁,就会进入这个状态
- WAITING 等待状态,当调用
Object.wait或者Thread.join()且没有设置时间,在或者LockSupport.park时,都会进入等待状态。 - TIMED_WAITING 计时等待,当调用
Thread.sleep()或者Object.wait(xx)或者Thread.join(xx)或者LockSupport.parkNanos或者LockSupport.partUntil时,进入该状态 - TERMINATED 终止状态,线程中断或者运行结束的状态
先来sleep和wait的区别
由于wait方法是在Object上的,而sleep方法是在Thread上,当调用Thread.sleep时,并不能改变对象的状态,因此也不会释放锁。

这让我想起来我家的两个主子,一只泰迪一只美短,虽然他们两个是不同的物种,但是却有着相同的爱好,就是爱吃牛肉。偶尔给它们两个开荤,奈何只有一个食盆,每次只能一个主子吃肉。这就好比是两个线程,在争用同一个变量。如果使用thread.sleep,那么其中一个吃完一块肉后,会霸占食盆,不给另一只吃(不会释放锁等资源);如果使用wait,那么吃肉时,会离开食盆,这样就有机会让另一只去吃了,即占用的资源会释放。
详细的看一下下面的代码:
package cn.xingoo.test.basic.thread;
public class AnimalEat {
public static void main(String[] args) {
System.out.println("盆里有20块肉");
Animal animal = new Animal();
try{
Thread tidy = new Thread(animal,"泰迪");
Thread cat = new Thread(animal,"美短");
tidy.start();
cat.start();
}catch (Exception e){
e.printStackTrace();
}
System.out.println("盆里的肉吃完了!");
}
}
class Animal implements Runnable {
int count = 0;
@Override
public void run() {
while(count < 20){
synchronized (this){
try {
System.out.println(Thread.currentThread().getName()+"吃力第"+count+"块肉");
count++;
//Thread.sleep(100);
this.wait(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
当使用this.wait(100)的时候,会输出下面的信息:
盆里有20块肉
泰迪吃力第0块肉
美短吃力第1块肉
盆里的肉吃完了!
泰迪吃力第2块肉
美短吃力第3块肉
泰迪吃力第4块肉
美短吃力第5块肉
泰迪吃力第6块肉
美短吃力第7块肉
泰迪吃力第8块肉
美短吃力第9块肉
泰迪吃力第10块肉
美短吃力第11块肉
美短吃力第12块肉
泰迪吃力第13块肉
美短吃力第14块肉
泰迪吃力第15块肉
美短吃力第16块肉
泰迪吃力第17块肉
美短吃力第18块肉
泰迪吃力第19块肉
可以发现,输出的信息并不是完美的交替,这是因为调用wait之后,并不一定马上时另一个线程执行,而是要根据CPU的时间分片轮转等其他的条件来定,轮到谁就看运气了。
当使用Thread.sleep(100)的时候,可以得到下面的信息:
盆里有20块肉
泰迪吃力第0块肉
盆里的肉吃完了!
泰迪吃力第1块肉
泰迪吃力第2块肉
泰迪吃力第3块肉
泰迪吃力第4块肉
泰迪吃力第5块肉
泰迪吃力第6块肉
泰迪吃力第7块肉
泰迪吃力第8块肉
泰迪吃力第9块肉
泰迪吃力第10块肉
泰迪吃力第11块肉
泰迪吃力第12块肉
泰迪吃力第13块肉
泰迪吃力第14块肉
泰迪吃力第15块肉
泰迪吃力第16块肉
泰迪吃力第17块肉
泰迪吃力第18块肉
美短吃力第19块肉
泰迪吃力第20块肉
注意看最后面有一只美短。这是因为synchronized的代码同步时在while循环里面,因此最后一次两个主子都进入到了while里面,然后才开始等待相应的锁。这就导致第19次轮到了另一个主子。
总结来说,sleep不会释放线程的锁,wait会释放线程的资源。
再谈谈wait与notify和notifyall
wait、notify、notifyall这几个一般都一起使用。不过需要注意下面几个重要的点:
- 调用wait\notify\notifyall方法时,需要与锁或者synchronized搭配使用,不然会报错
java.lang.IllegalMonitorStateException,因为任何时刻,对象的控制权只能一个线程持有,因此调用wait等方法的时候,必须确保对其的控制权。 - 如果对简单的对象调用wait等方法,如果对他们进行赋值也会报错,因为赋值相当于修改的原有的对象,因此如果有修改需求可以外面包装一层。
- notify可以唤醒一个在该对象上等待的线程,notifyAll可以唤醒所有等待的线程。
- wait(xxx) 可以挂起线程,并释放对象的资源,等计时结束后自动恢复;wait()则必须要其他线程调用notify或者notifyAll才能唤醒。

举个通俗点的例子,我记得在高中的时候,每天上午快放学的时候大家都很紧张——因为那个时候小饭馆正好播放一些港台剧,大家就总愿意抢电视机旁边的位置,所以每次快要中午放学的时候,大家都做好冲刺跑步的准备。
但是有的老师总愿意压堂,搞的大家怨声载道。
比如,下面这位老师有的时候会用notifyall通知大家集体放学;有的时候会检查背书,背好了,才能走。
package cn.xingoo.test.basic.thread;
public class School {
private DingLing dingLing = new DingLing(false);
class Teacher extends Thread{
Teacher(String name){
super(name);
}
@Override
public void run() {
synchronized (dingLing){
try {
dingLing.wait(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
dingLing.flag = true;
System.out.println("放学啦");
dingLing.notifyAll();
/*for (int i = 0; i < 3; i++) {
System.out.println("放一个走吧");
dingLing.notify();
try {
dingLing.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}*/
}
}
}
class Student extends Thread{
Student(String name){
super(name);
}
@Override
public void run(){
synchronized (dingLing){
while(!dingLing.flag){
System.out.println(Thread.currentThread().getName()+"开始等待");
try {
dingLing.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"去吃饭啦");
}
}
}
public static void main(String[] args) {
School school = new School();
Teacher teacher = school.new Teacher("老师");
Student zhangsan = school.new Student("张三");
Student lisi = school.new Student("李四");
Student wangwu = school.new Student("王五");
teacher.start();
zhangsan.start();
lisi.start();
wangwu.start();
}
}
class DingLing{
Boolean flag = false;
public DingLing(Boolean flag){
this.flag = flag;
}
}
当老师统一喊放学的时候,即调用dingLing.notifyAll();,会得到下面的输出:
张三开始等待
李四开始等待
王五开始等待
放学啦
王五去吃饭啦
李四去吃饭啦
张三去吃饭啦
如果检查背书,那么每次老师只会调用一次notify,让一个同学(线程)走(工作),就会得到下面的输出:
张三开始等待
李四开始等待
王五开始等待
放一个走吧
张三去吃饭啦
放一个走吧
李四去吃饭啦
放一个走吧
王五去吃饭啦
注意的是,调用wait可以释放dingling的占用,这样才能让别的线程进行检查,如果改成Thread.sleep,有兴趣的童鞋就可以自己去看看效果啦!
参考
- 最简单的实例说明wait、notify、notifyAll的使用方法:http://longdick.iteye.com/blog/453615
- Java sleep和wait的区别:http://www.jb51.net/article/113587.htm
- sleep和wait解惑:https://www.zhihu.com/question/23328075
通过两个小栗子来说说Java的sleep、wait、notify、notifyAll的用法的更多相关文章
- Java多线程的wait(),notify(),notifyAll()
在多线程的情况下.因为多个线程与存储空间共享相同的过程,同时带来的便利.它也带来了访问冲突这个严重的问题. Java语言提供了一种特殊的机制来解决这类冲突,避免同一数据对象由多个线程在同一时间访问. ...
- java 并发——理解 wait / notify / notifyAll
一.前言 前情简介: java 并发--内置锁 java 并发--线程 java 面试是否有被问到过,sleep 和 wait 方法的区别,关于这个问题其实不用多说,大多数人都能回答出最主要的两点区别 ...
- java中的wait(),notify(),notifyAll(),synchronized方法
wait(),notify(),notifyAll()三个方法不是Thread的方法,而是Object的方法.意味着所有对象都有这三个方法,因为每个对象都有锁,所以自然也都有操作锁的方法了.这三个方法 ...
- Java多线程:wait(),notify(),notifyAll()
1. wait(),notify(),notifyAll() 2. wait() 2.1. wait() 2.2. wait(long timeout) 2.3. wait(long timeout, ...
- Java多线程--wait(),notify(),notifyAll()的用法
忙等待没有对运行等待线程的 CPU 进行有效的利用(而且忙等待消耗cpu过于恐怖,请慎用),除非平均等待时间非常短.否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号. Java ...
- java 多线程(wait/notify/notifyall)
package com.example; public class App { /* wait\notify\notifyAll 都属于object的内置方法 * wait: 持有该对象的线程把该对象 ...
- 一个小栗子聊聊JAVA泛型基础
背景 周五本该是愉快的,可是今天花了一个早上查问题,为什么要花一个早上?我把原因总结为两点: 日志信息严重丢失,茫茫代码毫无头绪. 对泛型的认识不够,导致代码出现了BUG. 第一个原因可以通过以后编码 ...
- 20145229吴姗珊 《Java程序设计》两天小总结
20145229吴姗珊 <Java程序设计>两天小总结 教材学习内容总结 第十章 输入\输出 1.java将输入\输出抽象化为串流,数据有来源及目的地,衔接两者的是串流对象 2.从应用程序 ...
- 20145229吴姗珊两天小总结 《Java程序设计》第4周学习总结
20145229吴姗珊两天小总结 <Java程序设计>第4周学习总结 教材学习内容总结 由于自己的基础不好对知识的理解不透彻,所以这两天的学习还是集中在第六章和第七章,对知识点多了一点理解 ...
随机推荐
- C语言程序设计第一作业
C语言程序设计第一作业 实验总结 (一) 1.题目:输入圆的半径,求圆周长和面积 2.流程图: 3.测试数据及运行结果: 4.实验分析: 问题1: 出现了错误 原因:是在赋值那写反了 解决方法:应该是 ...
- vue2购物车ch1-(安装依赖、简单配置、 axios获取api的模拟数据)
0--项目说明 说明此项目源自某课网购物车教程,但是在开发过程中,发现在开发过程中用的还是 vue-resource(宣布不更新的类$.ajx()插件),为了以后的发展使用axios.js,详情参考 ...
- python pyqt
一.控件 1.单行文本框QLineText clear() 清除文本框中的内容contextMenuEvent() 右键菜单事件copy() 复制文本框中的内容cut() 剪切文本框中的内容paste ...
- OVS中arp响应的流表的实现
总结: 1.br-int 流表总体是按照Normal 的方式,即常规的交换机的转发方式进行转发.而br-tun 交换机则主要按照流表的方式进行转发. 2.一般情况下,VM发出的ARP请求,会在该VM的 ...
- [Python]Codecombat攻略之地牢Kithgard(1-22关)
首页:https://cn.codecombat.com/play语言:Python 第一界面:地牢 Kithgard(22关) 时间:1-3小时 内容:语法.方法.参数.字符串.循环.变量等 网页: ...
- hibernate使用注解简化开发
简述 在编写hibernate的时候,需要将实体类映射到数据库中的表.通常需要一个配置文件(hibernate.cfg.xml),一个实体类(XX.Java),还有一个映射文件(XX.hbm.xml) ...
- Java8之旅(六) -- 使用lambda实现尾递归
前言 本篇介绍的不是什么新知识,而是对前面讲解的一些知识的综合运用.众所周知,递归是解决复杂问题的一个很有效的方式,也是函数式语言的核心,在一些函数式语言中,是没有迭代与while这种概念的,因为此类 ...
- LeetCode 112. Path Sum (二叉树路径之和)
Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all ...
- 媲美jQuery的JS框架----AngularJS(二)
前言 对于AngularJS什么,小编在这就不多做介绍了.大家可以看小编的上一篇博客. 言归正传,小编在上一篇博客中介绍了AngularJS中的指令.表达式还有非常实用的三种服务.接下来,带大家看一看 ...
- 关于Python输出时间戳的问题
在我们的程序中,有时候想要知道程序的执行时间或者准确的停止时间,这时候就需要我们自己添加一个时间戳,以便我们做出判断和相应的处理. 下面是我亲测并收集的资料,菜鸟一枚,不全之处大神可给予补充和指正. ...