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

本文转自博客园xingoo的博客,原文链接:通过两个小栗子来说说Java的sleep、wait、notify、notifyAll的用法,如需转载请自行联系原博主。

时间: 2024-09-23 03:56:01

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

java join sleep wait notify notifyAll

sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响.该线程不丢失任何监视器的所属权. 通过调用sleep使任务进入休眠状态,在这种情况下,任务在指定的时间内不会运行.调用sleep的时候锁并没有被释放.休眠 Java SE5引入了更加显示的sleep()版本作为TimeUnit类的一部分,这个方法允许你指定sleep()延迟的时间单元,因此可以提供更好的可阅读性.TimeUnit还可以被用来执行转换,就想稍后你会在本书中看到的那样.

登陆需要 的密码 急求-求吧两个js小文件解析成java类

问题描述 求吧两个js小文件解析成java类 CSDN移动问答 解决方案 会java吗,了解java语法的话,直接对着实现呗,语言都是相通的,这个不难. 解决方案二: 这么久远的问题,还能被CSDN大冒险游戏翻出来,我都不好意思回答了.没办法,弹出的问题太少了,都被我回答了. 这是个大bug,一定要报告管理员叔叔.

session的两个小案例

版权声明:尊重博主原创文章,转载请注明出处哦~http://blog.csdn.net/eson_15/article/details/51262736 目录(?)[+]         学完了session,写两个小案例加深一下对session的巩固. 1. 用户登陆案例         登陆html页面提交后,将参数带给处理登陆的servlet,该servlet将获得登陆的用户名和密码,并将这些信息存入session中,另一个servlet在处理的时候,会先从session中拿到用户的信息,

代码发布!两个小函数让你的ASP程序对SQL注入免疫!

sql|程序|函数|函数 Rem ## 长整数转换 Function toNum(s, default) If IsNumeric(s) and s <> "" then toNum = CLng(s) Else toNum = default End If End Function Rem ## SQL 语句转换 Function toSql(str) If IsNull(str) Then str = "" toSql = replace(str,

系统音量调节的两个小技巧

音量调节本来是一个很简单的动作,只是按按F11或F12来降低或增大音量就可以了,下面的两个小技巧有时也能派上用场,不妨试用下: 调节时静音:你一定会发现每次按F11或F12的时候都会伴随有一个音效,让用户知道当前的系统音量水平,这本来是一个很贴心的设计,但有时候环境很安静的时候你可能不希望系统发出这样不和谐的声音,其实很简单,只要你按着Shift键不放,再去使用F11或F12调节音量,你就会发现系统不再在调节的同时发声了. 1/4格的调整:如果你单线用F11或F12去调节音量,你会发现增加或减小

ios-oc中有哪些输出符?具体是什么样子的呢?还有两个小问题

问题描述 oc中有哪些输出符?具体是什么样子的呢?还有两个小问题 输出的时候%g和%gi是什么意思呢 还有 @property和@synthesize 是什么意思呀 求解答~~~ 解决方案 http://zhidao.baidu.com/link?url=SFQtMTTm1svgk4Sphj5Ab0K3EvZP-tPR2vxeopqtIeP_KaaMJJDKH-bf3MVGmEWgKB2cmuPadGNPJZDy-UkP33Rds3Ms91YbKAmznpY-Y5G 解决方案二: %g 选用%f

http-HTTP报文的两个小问题

问题描述 HTTP报文的两个小问题 1.如何发送二进制数据?即格式是什么,以便于区别二进制文本? 2.请求行的HTTP版本与响应行的HTTP版本必须相同吗?如果不同会有什么影响? 解决方案 问题1: http 协议规定了报文的格式,发送方按照http协议组织报文,接收方按照http协议拆解报文并组装回应报文. 详细的报文格式参考:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/28/2612910.html 问题2: http协议不同的版本,

统一面桶底部有两只小活虫

近日,柳先生购买了一桶统一桶装老坛酸菜面,准备拆开塑封膜食用时,发现面桶底部有两只小活虫.对此,统一方面表示,出现活虫是因为在仓储过程中存在问题,与统一方便面的品质没有关系. 8月22日,柳先生在一家小卖部买了一桶统一桶装老坛酸菜面,"准备撕开塑封膜时,发现在塑封膜底部有小虫在爬".柳先生没有拆开外部塑封膜,并根据面桶上的热线电话向统一公司反映此事.次日,统一工作人员来到柳先生家了解情况,并称可赔偿柳先生10桶面,被柳先生拒绝.工作人员告诉柳先生稍后会有人给他答复,但直到9月4日也没有

前台是个图标,&amp;amp;lt;img&amp;amp;gt;图标。 怎么调用JSP作成的日历页面(一个小页面,用java + html代码,不是js)

问题描述 前台是个图标,<img>图标.怎么调用JSP作成的日历页面(一个小页面,用java+html代码,不是js)怎么调用这个jsp日历呢??另外在这个JSP里面输入的值,是怎么传回呢?? 解决方案 解决方案二:我也等回答.....解决方案三:该回复于2010-12-03 10:18:23被版主删除解决方案四:吧图片做成个链接<ahref="***.jsp"><imgsrc=""></a>解决方案五:可以将你的be