通过两个小栗子来说说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;
}

如下图所示:

  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
时间: 2024-08-30 17:29:58

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

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

忙等待没有对运行等待线程的 CPU 进行有效的利用(而且忙等待消耗cpu过于恐怖,请慎用),除非平均等待时间非常短.否则,让等待线程进入睡眠或者非运行状态更为明智,直到它接收到它等待的信号. Java 有一个内建的等待机制来允许线程在等待信号的时候变为非运行状态.java.lang.Object 类定义了三个方法,wait().notify()和 notifyAll()来实现这个等待机制. 但在使用wait().notify()和 notifyAll()必须获取该对象的锁,否则的话会抛异常Ill

java 多线程(wait/notify/notifyall)

package com.example; public class App { /* wait\notify\notifyAll 都属于object的内置方法 * wait: 持有该对象的线程把该对象的控制权交出 notify: 通知某个正在等待该对象控制权的线程可以继续运行 notifyAll: 通知所有等待该对象控制权的线程继续运行 */ public static void main(String[] args) { MyThread mt = new MyThread(); mt.sta

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

背景 周五本该是愉快的,可是今天花了一个早上查问题,为什么要花一个早上?我把原因总结为两点: 日志信息严重丢失,茫茫代码毫无头绪. 对泛型的认识不够,导致代码出现了BUG. 第一个原因可以通过以后编码谨慎的打日志来解决,我们今天主要来一起回顾下JAVA泛型基础. 一个小栗子 先看下面一个例子,test1实例化一个List容器的时候没有指定泛型参数,那么我们可以往这个容器里面放入任何类型的对象,这样是不是很爽?但是当我们从容器中取出容器中的对象的时候我们必须小心翼翼,因为容器中的对象具有运行时的类

Javaweb统计在线人数的小栗子

最近在学习Javaweb相关的内容(不黑不吹之前对web开发零基础),下面通过一个统计在线人数的小栗子讲讲Servlet监听器吧 开发环境 eclipse  tomcat 7 先说说这个小栗子的构思: 首先要考虑的就是通过什么方式能够统计在线人数?很容易想到可以通过session来统计在线人数为什么不是request呢?因为request在请求结束时即服务器返回资源时就被销毁了,也就是说request的作用域不足以用以统计在线人数.服务器在第一次接受一个用户请求时会给该用户分配一个session

分享两个你可能不知道的Java小秘密

引言 最近LZ的工作发生了重大变化,以后博文的更新速度可能会再度回温,希望猿友们可以继续关注. 近期LZ辞掉了项目经理的工作,不过并未离开公司,是转到了基础研发部做更基础的研发,为广大技术人员服务.这会让LZ有更多的时间去研究一些技术方面的东西,LZ打算折腾一下spring的源码,期待有一天可以成为spring代码的贡献者. 好了,废话说到这里吧,今天先分享两个小问题的解决办法,可能你以后也会遇到的. DBCP数据源坑爹的地方 前几天系统出现了一个错误,比较奇葩.中文解释是“无法从套接字读取更多

session的两个小案例

学完了session,写两个小案例加深一下对session的巩固. 1. 用户登陆案例 登陆html页面提交后,将参数带给处理登陆的servlet,该servlet将获得登陆的用户名和密码,并将这些信息存入session中,另一个servlet在处理的时候,会先从session中拿到用户的信息,判断是否登陆,再执行相应的动作.主要模拟一下我们常见的场景:登陆后,网页上会显示:欢饮您,xxx. 首先我们看一下欢迎页面index.jsp(这里只是简单的模拟下,后面学了jsp的标签后可以弄的更加人性化

数组去重的两个小技巧

数组去重的两个小技巧 首先es6有提供了 Set 对象, 同java中的 Set, set中的值不可以重复. 所以对于一个数组我们可以直接采用 const duplicateArr = [2,3,4,4,4,3]; const newArray = [...new Set(duplicateArr)]; console.log(newArray); 可以采用filter函数进行过滤, 查看index 是否跟当前的index一致. 因为indexOf返回的是第一个查找的位置 const dupli

用两个小例子来解释单例模式中的“双重锁定”

学习单例模式时,好多人都不太理解双重锁定.学完后突然想到一个很有趣的例子. 单例模式结构图: 代码: Singleton类 class Singleton { private static Singleton instance; private static readonly object syncRoot = new object(); //程序运行时创建一个静态只读的进程辅助对象 private Singleton() { } //用private修饰构造方法,防止外界利用new创建此类实例

ASP.NET MVC 开发中遇到的两个小问题

最近在做一个网站,用asp.net MVC4.0来开发,今天遇到了两个小问题,通过查找相关渠道解决了,在这里把这两个问题写出来,问题非常简单,不喜勿喷,mark之希望可以给遇到相同问题的初学者一点帮助. 问题一:我要实现文件的异步上传,前端提交的文件后台老是获取不到. 我前端是这样写的: @using (Ajax.BeginForm("UpdateNewInfo", "Home", new AjaxOptions() { HttpMethod = "pos