• Java 多线程 线程的五种状态,线程 Sleep, Wait, notify, notifyAll


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

    1 public enum State {
    2     NEW,
    3     RUNNABLE,
    4     BLOCKED,
    5     WAITING,
    6     TIMED_WAITING,
    7     TERMINATED;
    8 }

    具体解释请见源码,下面简单解释下Thread的五种状态什么时候出现:

    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时,并不能改变对象的状态,因此也不会释放锁。请看下面代码结果:

     1 package springBootExample.example.simpleApplication;
     2 
     3 public class TestThread {
     4 
     5     public static void main(String[] args) {
     6         Room room = new Room();
     7         Thread man = new Thread(room, "男人");
     8         Thread female = new Thread(room, "女人");
     9         System.out.println("After new but before start thread name = "+man.getName()+" state = "+man.getState());
    10         // 此时的man和female处于NEW状态
    11         man.start();
    12         System.out.println("After start Thread name ="+man.getName()+" state = "+man.getState());
    13         female.start();
    14         // 此时的man和female处于Runnable状态,但是等待相应的资源(比如IO或者时间片切换)才能开始执行,谁先获得资源就可以执行
    15         System.out.println("小姐已经接待完客人");
    16     }
    17 
    18 }
    19 
    20 class Room implements Runnable {
    21     public int count = 1;
    22 
    23     @Override
    24     public void run() {
    25 
    26         while (count <= 20) {
    27             // BLOCKED 阻塞状态,当遇到synchronized或者lock且没有取得相应的锁,就会进入这个状态
    28             synchronized (this) {
    29                 System.out.println(Thread.currentThread().getName() + "去小姐的房间,小姐累计接待客人:" + count + "个.");
    30                 count++;
    31                 try {
    32                     Thread.currentThread().sleep(100);
    33                     // this.wait(100);
    34                 } catch (InterruptedException e) {
    35                     // TODO Auto-generated catch block
    36                     e.printStackTrace();
    37                 }
    38             }
    39         }
    40 
    41     }
    42 
    43 }

    结果:

     1 After new but before start thread name = 男人 state = NEW
     2 After start Thread name =男人 state = RUNNABLE
     3 男人去小姐的房间,小姐累计接待客人:1个.
     4 小姐已经接待完客人
     5 男人去小姐的房间,小姐累计接待客人:2个.
     6 男人去小姐的房间,小姐累计接待客人:3个.
     7 男人去小姐的房间,小姐累计接待客人:4个.
     8 男人去小姐的房间,小姐累计接待客人:5个.
     9 男人去小姐的房间,小姐累计接待客人:6个.
    10 男人去小姐的房间,小姐累计接待客人:7个.
    11 男人去小姐的房间,小姐累计接待客人:8个.
    12 男人去小姐的房间,小姐累计接待客人:9个.
    13 男人去小姐的房间,小姐累计接待客人:10个.
    14 男人去小姐的房间,小姐累计接待客人:11个.
    15 男人去小姐的房间,小姐累计接待客人:12个.
    16 男人去小姐的房间,小姐累计接待客人:13个.
    17 男人去小姐的房间,小姐累计接待客人:14个.
    18 男人去小姐的房间,小姐累计接待客人:15个.
    19 男人去小姐的房间,小姐累计接待客人:16个.
    20 男人去小姐的房间,小姐累计接待客人:17个.
    21 男人去小姐的房间,小姐累计接待客人:18个.
    22 男人去小姐的房间,小姐累计接待客人:19个.
    23 男人去小姐的房间,小姐累计接待客人:20个.
    24 女人去小姐的房间,小姐累计接待客人:21个.

    从上面的结果可以看出,NEW状态在新创建一个线程时呈现,RUNNABLE是在线程调用start()方法。因为线程获得资源就可以执行,在main()方法中新建一个线程man.start()执行,因此新线程获得资源就可以执行,从第4行结果看出。

    注意看最后面有一个女人。这是因为synchronized的代码同步时在while循环里面,因此最后一次男人和女人都进入到了while里面,然后才开始等待相应的锁。这就导致第20次执行完轮到了女人。

    当调用wait时:

     1 @Override
     2     public void run() {
     3 
     4         while (count <= 20) {
     5             // BLOCKED 阻塞状态,当遇到synchronized或者lock且没有取得相应的锁,就会进入这个状态
     6 //            System.out.println("Before synchronized thread name = "+Thread.currentThread().getName()+" state = "+Thread.currentThread().getState());
     7             synchronized (this) {
     8                 System.out.println(Thread.currentThread().getName() + "去小姐的房间,小姐累计接待客人:" + count + "个.");
     9                 count++;
    10                 try {
    11 //                    Thread.currentThread().sleep(100);
    12                      this.wait(100);
    13                 } catch (InterruptedException e) {
    14                     // TODO Auto-generated catch block
    15                     e.printStackTrace();
    16                 }
    17             }
    18         }

    结果:

     1 After new but before start thread name = 男人 state = NEW
     2 After start Thread name =男人 state = RUNNABLE
     3 小姐已经接待完客人
     4 男人去小姐的房间,小姐累计接待客人:1个.
     5 女人去小姐的房间,小姐累计接待客人:2个.
     6 男人去小姐的房间,小姐累计接待客人:3个.
     7 女人去小姐的房间,小姐累计接待客人:4个.
     8 女人去小姐的房间,小姐累计接待客人:5个.
     9 男人去小姐的房间,小姐累计接待客人:6个.
    10 女人去小姐的房间,小姐累计接待客人:7个.
    11 男人去小姐的房间,小姐累计接待客人:8个.
    12 男人去小姐的房间,小姐累计接待客人:9个.
    13 女人去小姐的房间,小姐累计接待客人:10个.
    14 男人去小姐的房间,小姐累计接待客人:11个.
    15 女人去小姐的房间,小姐累计接待客人:12个.
    16 男人去小姐的房间,小姐累计接待客人:13个.
    17 女人去小姐的房间,小姐累计接待客人:14个.
    18 男人去小姐的房间,小姐累计接待客人:15个.
    19 女人去小姐的房间,小姐累计接待客人:16个.
    20 男人去小姐的房间,小姐累计接待客人:17个.
    21 女人去小姐的房间,小姐累计接待客人:18个.
    22 男人去小姐的房间,小姐累计接待客人:19个.
    23 女人去小姐的房间,小姐累计接待客人:20个.

    但是如果稍作修改就会出现弄一种情况,代码如下:

     1 class Room implements Runnable {
     2     public int count = 1;
     3 
     4     @Override
     5     public void run() {
     6 
     7         while (count <= 20) {
     8             // BLOCKED 阻塞状态,当遇到synchronized或者lock且没有取得相应的锁,就会进入这个状态
     9             System.out.println("Before synchronized thread name = "+Thread.currentThread().getName()+" state = "+Thread.currentThread().getState());
    10             synchronized (this) {
    11                 System.out.println(Thread.currentThread().getName() + "去小姐的房间,小姐累计接待客人:" + count + "个.");
    12                 count++;
    13                 try {
    14                     Thread.currentThread().sleep(100);
    15                     // this.wait(100);
    16                 } catch (InterruptedException e) {
    17                     // TODO Auto-generated catch block
    18                     e.printStackTrace();
    19                 }
    20             }
    21         }
    22 
    23     }

    结果:

     1 After new but before start thread name = 男人 state = NEW
     2 After start Thread name =男人 state = RUNNABLE
     3 Before synchronized thread name = 男人 state = RUNNABLE
     4 小姐已经接待完客人
     5 男人去小姐的房间,小姐累计接待客人:1个.
     6 Before synchronized thread name = 女人 state = RUNNABLE
     7 Before synchronized thread name = 男人 state = RUNNABLE
     8 女人去小姐的房间,小姐累计接待客人:2个.
     9 Before synchronized thread name = 女人 state = RUNNABLE
    10 男人去小姐的房间,小姐累计接待客人:3个.
    11 Before synchronized thread name = 男人 state = RUNNABLE
    12 女人去小姐的房间,小姐累计接待客人:4个.
    13 Before synchronized thread name = 女人 state = RUNNABLE
    14 男人去小姐的房间,小姐累计接待客人:5个.
    15 Before synchronized thread name = 男人 state = RUNNABLE
    16 女人去小姐的房间,小姐累计接待客人:6个.
    17 Before synchronized thread name = 女人 state = RUNNABLE
    18 男人去小姐的房间,小姐累计接待客人:7个.
    19 Before synchronized thread name = 男人 state = RUNNABLE
    20 女人去小姐的房间,小姐累计接待客人:8个.
    21 Before synchronized thread name = 女人 state = RUNNABLE
    22 男人去小姐的房间,小姐累计接待客人:9个.
    23 Before synchronized thread name = 男人 state = RUNNABLE
    24 女人去小姐的房间,小姐累计接待客人:10个.
    25 Before synchronized thread name = 女人 state = RUNNABLE
    26 男人去小姐的房间,小姐累计接待客人:11个.
    27 Before synchronized thread name = 男人 state = RUNNABLE
    28 女人去小姐的房间,小姐累计接待客人:12个.
    29 Before synchronized thread name = 女人 state = RUNNABLE
    30 男人去小姐的房间,小姐累计接待客人:13个.
    31 Before synchronized thread name = 男人 state = RUNNABLE
    32 女人去小姐的房间,小姐累计接待客人:14个.
    33 Before synchronized thread name = 女人 state = RUNNABLE
    34 男人去小姐的房间,小姐累计接待客人:15个.
    35 Before synchronized thread name = 男人 state = RUNNABLE
    36 女人去小姐的房间,小姐累计接待客人:16个.
    37 Before synchronized thread name = 女人 state = RUNNABLE
    38 男人去小姐的房间,小姐累计接待客人:17个.
    39 Before synchronized thread name = 男人 state = RUNNABLE
    40 女人去小姐的房间,小姐累计接待客人:18个.
    41 Before synchronized thread name = 女人 state = RUNNABLE
    42 男人去小姐的房间,小姐累计接待客人:19个.
    43 Before synchronized thread name = 男人 state = RUNNABLE
    44 女人去小姐的房间,小姐累计接待客人:20个.
    45 男人去小姐的房间,小姐累计接待客人:21个.

    目前这种现象暂时还不是特别清楚原理,但是当男人和女人都在while循环等待时,Thread.currentThread().getName() 会获取当前线程的名字,而在循环中再获取当前名字时会出现这种交替的情况?其实Room资源一直是男人拥有。

    三、Wait(), Notify() , NotifyAll()的使用

    wait、notify、notifyall这几个一般都一起使用。不过需要注意下面几个重要的点:

    1. 调用wait otify otifyall方法时,需要与锁或者synchronized搭配使用,不然会报错java.lang.IllegalMonitorStateException,因为任何时刻,对象的控制权只能一个线程持有,因此调用wait等方法的时候,必须确保对其的控制权。
    2. 如果对简单的对象调用wait等方法,如果对他们进行赋值也会报错,因为赋值相当于修改的原有的对象,因此如果有修改需求可以外面包装一层。
    3. notify可以唤醒一个在该对象上等待的线程,notifyAll可以唤醒所有等待的线程。
    4. wait(xxx) 可以挂起线程,并释放对象的资源,等计时结束后自动恢复;wait()则必须要其他线程调用notify或者notifyAll才能唤醒。
     1 package springBootExample.example.simpleApplication;
     2 
     3 public class TestWaitAndNotify {
     4     Call call = new Call(false);
     5 
     6     class MaMa extends Thread {
     7         public MaMa(String name) {
     8             super(name);
     9         }
    10 
    11         @Override
    12         public void run() {
    13             synchronized (call) {
    14                 try {
    15                     call.wait(3000);
    16                 } catch (InterruptedException e) {
    17                     // TODO Auto-generated catch block
    18                     e.printStackTrace();
    19                 }
    20                 call.setFlag(true);
    21                 // call.notifyAll();
    22                 for (int i = 0; i < 3; i++) {
    23                     System.out.println("进来一个吧");
    24                     call.notify();
    25                     try {
    26                         call.wait(1000);
    27                     } catch (InterruptedException e) {
    28                         e.printStackTrace();
    29                     }
    30                 }
    31             }
    32 
    33         }
    34 
    35     }
    36 
    37     class Customer extends Thread {
    38         public Customer(String name) {
    39             super(name);
    40         }
    41 
    42         @Override
    43         public void run() {
    44             synchronized (call) {
    45                 while (!call.isFlag()) {
    46                     System.out.println(Thread.currentThread().getName() + "等待王妈妈的呼唤");
    47                     try {
    48                         call.wait();
    49                     } catch (InterruptedException e) {
    50                         // TODO Auto-generated catch block
    51                         e.printStackTrace();
    52                     }
    53                 }
    54                 System.out.println(Thread.currentThread().getName() + "进入小姐的房间");
    55             }
    56         }
    57     }
    58 
    59     public static void main(String[] args) {
    60         TestWaitAndNotify test = new TestWaitAndNotify();
    61         MaMa teacher = test.new MaMa("王妈妈");
    62         Customer stu1 = test.new Customer("小米");
    63         Customer stu2 = test.new Customer("小百");
    64         Customer stu3 = test.new Customer("小阿");
    65         teacher.start();
    66         stu1.start();
    67         stu2.start();
    68         stu3.start();
    69 
    70     }
    71 
    72 }
    73 
    74 class Call {
    75     private boolean flag = false;
    76 
    77     public Call(boolean flag) {
    78         this.flag = flag;
    79     }
    80 
    81     public boolean isFlag() {
    82         return flag;
    83     }
    84 
    85     public void setFlag(boolean flag) {
    86         this.flag = flag;
    87     }
    88 
    89 }

    上面代码中21,24行包含了notify() 和notifyAll()方法的,61行注意内部类实例时的方法。代码的运行结果也会不相同,notify()输出的结果为:

    小米等待王妈妈的呼唤
    小阿等待王妈妈的呼唤
    小百等待王妈妈的呼唤
    进来一个吧
    小米进入小姐的房间
    进来一个吧
    小阿进入小姐的房间
    进来一个吧
    小百进入小姐的房间

    notifyAll()输出的结果为:

    小米等待王妈妈的呼唤
    小阿等待王妈妈的呼唤
    小百等待王妈妈的呼唤
    小百进入小姐的房间
    小阿进入小姐的房间
    小米进入小姐的房间

    Reference

    [1] http://www.cnblogs.com/xing901022/p/7846809.html

  • 相关阅读:
    syslog(),closelog()与openlog()--日志操作函数 (1)
    C语言之strrchr函数
    HTTP 报文
    Apache Hive 建表操作的简单描述
    Apache Hive 简介及安装
    Hadoop之MapReduce(二)序列化,排序及分区
    Hadoop之MapReduce(一)简介及简单案例
    Hadoop之HDFS(三)HDFS的JAVA API操作
    Hadoop之HDFS(二)HDFS基本原理
    Hadoop之HDFS(一)HDFS入门及基本Shell命令操作
  • 原文地址:https://www.cnblogs.com/hoojjack/p/7932107.html
Copyright © 2020-2023  润新知