线程是计算程序运行的最小载体,由于单个单核CPU的硬件水平发展到了一定的瓶颈期,因此就出现了多核多CPU的情况,直接就导致程序员多线程编程的复杂。由此可见线程对于高性能开发的重要性。

那么线程在计算机中有好几种状态,他们之间是怎么切换的?sleep和wait又有什么区别?notify和notifyAll怎么用?带着这些问题,我们来看看Java的线程吧!

Thread的状态

先来看看Thread类里面都有哪几种状态,在Thread.class中可以找到这个枚举,它定义了线程的相关状态:

public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}

如下图所示:

  1. NEW 新建状态,线程创建且没有执行start方法时的状态
  2. RUNNABLE 可运行状态,线程已经启动,但是等待相应的资源(比如IO或者时间片切换)才能开始执行
  3. BLOCKED 阻塞状态,当遇到synchronized或者lock且没有取得相应的锁,就会进入这个状态
  4. WAITING 等待状态,当调用Object.wait或者Thread.join()且没有设置时间,在或者LockSupport.park时,都会进入等待状态。
  5. TIMED_WAITING 计时等待,当调用Thread.sleep()或者Object.wait(xx)或者Thread.join(xx)或者LockSupport.parkNanos或者LockSupport.partUntil时,进入该状态
  6. 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这几个一般都一起使用。不过需要注意下面几个重要的点:

  1. 调用wait\notify\notifyall方法时,需要与锁或者synchronized搭配使用,不然会报错java.lang.IllegalMonitorStateException,因为任何时刻,对象的控制权只能一个线程持有,因此调用wait等方法的时候,必须确保对其的控制权。
  2. 如果对简单的对象调用wait等方法,如果对他们进行赋值也会报错,因为赋值相当于修改的原有的对象,因此如果有修改需求可以外面包装一层。
  3. notify可以唤醒一个在该对象上等待的线程,notifyAll可以唤醒所有等待的线程。
  4. 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,有兴趣的童鞋就可以自己去看看效果啦!

参考

  1. 最简单的实例说明wait、notify、notifyAll的使用方法:http://longdick.iteye.com/blog/453615
  2. Java sleep和wait的区别:http://www.jb51.net/article/113587.htm
  3. sleep和wait解惑:https://www.zhihu.com/question/23328075

通过两个小栗子来说说Java的sleep、wait、notify、notifyAll的用法的更多相关文章

  1. Java多线程的wait(),notify(),notifyAll()

    在多线程的情况下.因为多个线程与存储空间共享相同的过程,同时带来的便利.它也带来了访问冲突这个严重的问题. Java语言提供了一种特殊的机制来解决这类冲突,避免同一数据对象由多个线程在同一时间访问. ...

  2. java 并发——理解 wait / notify / notifyAll

    一.前言 前情简介: java 并发--内置锁 java 并发--线程 java 面试是否有被问到过,sleep 和 wait 方法的区别,关于这个问题其实不用多说,大多数人都能回答出最主要的两点区别 ...

  3. java中的wait(),notify(),notifyAll(),synchronized方法

    wait(),notify(),notifyAll()三个方法不是Thread的方法,而是Object的方法.意味着所有对象都有这三个方法,因为每个对象都有锁,所以自然也都有操作锁的方法了.这三个方法 ...

  4. Java多线程:wait(),notify(),notifyAll()

    1. wait(),notify(),notifyAll() 2. wait() 2.1. wait() 2.2. wait(long timeout) 2.3. wait(long timeout, ...

  5. Java多线程--wait(),notify(),notifyAll()的用法

    忙等待没有对运行等待线程的 CPU 进行有效的利用(而且忙等待消耗cpu过于恐怖,请慎用),除非平均等待时间非常短.否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号. Java ...

  6. java 多线程(wait/notify/notifyall)

    package com.example; public class App { /* wait\notify\notifyAll 都属于object的内置方法 * wait: 持有该对象的线程把该对象 ...

  7. 一个小栗子聊聊JAVA泛型基础

    背景 周五本该是愉快的,可是今天花了一个早上查问题,为什么要花一个早上?我把原因总结为两点: 日志信息严重丢失,茫茫代码毫无头绪. 对泛型的认识不够,导致代码出现了BUG. 第一个原因可以通过以后编码 ...

  8. 20145229吴姗珊 《Java程序设计》两天小总结

    20145229吴姗珊 <Java程序设计>两天小总结 教材学习内容总结 第十章 输入\输出 1.java将输入\输出抽象化为串流,数据有来源及目的地,衔接两者的是串流对象 2.从应用程序 ...

  9. 20145229吴姗珊两天小总结 《Java程序设计》第4周学习总结

    20145229吴姗珊两天小总结 <Java程序设计>第4周学习总结 教材学习内容总结 由于自己的基础不好对知识的理解不透彻,所以这两天的学习还是集中在第六章和第七章,对知识点多了一点理解 ...

随机推荐

  1. VirtualBox 磁盘容量调整

    起因 此前用VBox,初始时动态或固定分配的磁盘容量用完了就再重新建一个磁盘,但是有个虚拟机的系统分区容量不够了,把整个磁盘都分成系统盘仍然不够,于是研究了下磁盘容量调整问题. 正文 1,从当前虚拟机 ...

  2. wxPython中菜单、按钮学习

    ---恢复内容开始--- wx.Window 是一个基类,许多构件从它继承.包括 wx.Frame 构件.技术上这意味着,我们可以在所有的 子类中使用 wx.Window 的方法.我们这里介绍它的几种 ...

  3. JavaScript 版数据结构与算法(四)集合

    今天,我们要讲的是数据结构与算法中的集合. 集合简介 什么是集合?与栈.队列.链表这些顺序数据结构不同,集合是一种无序且唯一的数据结构.集合有什么用?在 Python 中,我经常使用集合来给数组去重: ...

  4. java中重载变长参数方法

    一.测试代码 package com.demo; public class Interview { public static void test(int i){ System.out.println ...

  5. 易语言关于使用CURL,网页_访问,网页_访问S,网页_访问_对象,鱼刺(winHttpW)发送Get性能测试

    易语言关于使用 CURL,网页_访问,网页_访问S,网页_访问_对象,鱼刺(winHttpW)发送Get性能测试 测试模块情况: |-精易模块5.8  |-鱼刺类Http  |-libCURL +++ ...

  6. SAP 动态设置 GUI STATUS 灰色不可用 或者隐藏(转)

    http://blog.sina.com.cn/s/blog_66110f6201017rul.html 有时候需要根据用户的权限或者是操作动态设置gui状态上的某些按钮的可用和不可用.   1.先定 ...

  7. ABAP 内表数据 与 Json串 相互转换

    内表: A B C IMINGZHA  HAIMINGZ AIMINGZH 1 2 3 4 5 6 Json串:  [{a: "IMINGZHA", b: "HAIMIN ...

  8. Android架构: MVC 新浪微博

    由于项目的需要,最近研究了一下需要连接网络项目的MVC架构,参考了一下一个新浪微博的开发架构 http://www.open-open.com/lib/view/open1345524526767.h ...

  9. Ubuntu使用Windows下的conio.h

    把虚线框里面的内容粘贴进文档文本里面 --------------------------------------------------------------------------------- ...

  10. IDEA启动后页面没有tomcat server选项,显示灰色问号和红叉不能使用

    说明:自己好几次硬盘莫名其妙读不出来导致电脑重启后idea没有了tomcat选项,原来的tomcat上显示灰色的问号和红色小叉子,网上搜了好久加上自己摸索,终于解决了.现在记一下也分享一下,省的下回又 ...