天天看点

【Java基础进阶笔记】- Day06 - 第三章 线程状态Java基础进阶笔记 - Day06 - 第三章 线程状态

Java基础进阶笔记 - Day06 - 第三章 线程状态

  • Java基础进阶笔记 - Day06 - 第三章 线程状态
    • 3.1 线程状态概述
    • 3.2 TIMED_WAITING(计时等待)
    • 3.3 BLOCKED(锁阻塞)
    • 3.4 WAITING(无限等待)
    • 3.5 补充知识点

Java基础进阶笔记 - Day06 - 第三章 线程状态

系统:Win10

JDK:1.8.0_121

IDE:IntelliJ IDEA 2017.3.7

3.1 线程状态概述

当线程被创建并启动之后,它既不是一起动就进入执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举类中给出了六种线程状态:

这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细的解析

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒动作时),该线程进入Waiting状态。进入这个状态以后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能唤醒
Timed_Waiting(计时等待) 同Waiting状态,有几个方法有超时参数,代用他们将进入Timed Waiting状态。这一状态将一直保持超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait
Terminated (被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡

目前不需要去研究这几种状态的实现原理,只需要知道在做线程操作中存在这样的状态。那么如何去理解这几个状态,新建与被终止还是很容易理解的,那么就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题

3.2 TIMED_WAITING(计时等待)

Timed_Waiting在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。单独的去理解这句话,可能不是很好理解,其实前面我们已经接触过这个状态了

在我们写的卖票案例中,模拟卖票前操作,我们在run方法中添加了sleep语句,这样就强制当前正在执行的线程休眠(暂停执行)

其实当我们调用了sleep方法之后,当前执行的线程就进入到了“休眠状态”,就是所谓的Timed_Waiting(计时等待),下面我们通过一个案例加深对该状态的一个理解

实现一个计时器,从0计数到59,在每个数字之间暂停一秒(模拟一分钟倒计时)

代码如下

public class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 60; i++) {
            System.out.println(i);
            try {
                // 线程睡眠1秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new MyThread().start();
    }
}
           

运行结果

【Java基础进阶笔记】- Day06 - 第三章 线程状态Java基础进阶笔记 - Day06 - 第三章 线程状态

通过案例可以发现,sleep方法的使用还是很简单的。我们需要记住下面几点:

  1. 进入TIMED_WAITING状态的一种常见情形是调用sleep方法,单独的线程也可以调用,不一定非要有协作关系
  2. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放在线程run()之内,这样才能保证该线程执行过程中睡眠
  3. sleep与锁无关,线程睡眠到期会自动苏醒,并返回到Runnable(可运行)状态
小提示:sleep()中指定的时间是线程不会运行的最短时间,因此sleep()方法不能保证该线程睡眠到期后会立刻开始执行

Timed_Waiting线程状态图

【Java基础进阶笔记】- Day06 - 第三章 线程状态Java基础进阶笔记 - Day06 - 第三章 线程状态

3.3 BLOCKED(锁阻塞)

Blocked状态在API中的介绍为:一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态

我们前面已经学完同步机制,那么这个状态是非常好理解的,比如线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态

这是由Runnable状态进入Blocked状态,除此Waiting以及Time_Waiting状态也会在某种情况下进入阻塞状态

Blocked状态线程状态图

【Java基础进阶笔记】- Day06 - 第三章 线程状态Java基础进阶笔记 - Day06 - 第三章 线程状态

3.4 WAITING(无限等待)

Waiting状态在API中的介绍为:一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态

下面我们通过一段代码来了解下

/**
 * 线程一先获取到锁对象,在同步代码块中调用wait方法,释放锁,进入无限等待状态-waiting
 * 线程二开始执行,获取到线程一释放的锁,执行完毕,调用notify唤醒含有wait方法的线程
 * 线程一获取到锁,从无限等待状态醒来,继续执行
 */
public class WaitingDemo {
    public static Object obj = new Object(); // 锁对象

    public static void main(String[] args) {
        // 演示waiting
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (obj) {
                    try {
                        System.out.println(Thread.currentThread().getName() + "获取到锁对象,调用wait方法,进入waiting状态,释放锁对象!");
                        obj.wait(); // 无限等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "从waiting状态醒来,获取到锁对象,继续执行!");
                }
            }
        }, "线程一").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + "开始执行!");
                    Thread.sleep(1000); // 睡眠1秒,让线程一先获取到锁对象
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj) {
                    System.out.println(Thread.currentThread().getName() + "获取到锁对象,执行完毕,调用notify方法,释放锁对象!");
                    obj.notify(); // 唤醒一个含有wait()方法的线程
                }
            }
        }, "线程二").start();
    }
}
           

运行结果

【Java基础进阶笔记】- Day06 - 第三章 线程状态Java基础进阶笔记 - Day06 - 第三章 线程状态

通过上面案例我们可以发现,一个调用了某个对象的Object.wait方法的线程会等待另一个线程调用此对象的Object.notify()对象或Object.notifyAll()方法

其实waiting状态并不是一个线程的操作,它体现的是多个线程的通信,可以理解为多个线程之间的协作关系,多个线程会争取锁,同时相互之间又存在协作关系。就好比在公司里的你和你的同事,你们可能存在晋升时的竞争,但更多的时候是一起合作以完成某些任务

当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态调用了wait()方法,那么A线程就进入Waiting(无限等待)状态,同时失去同步锁。假如这个时候线程B获取到同步锁,在运行状态中调用了notify()方法,那么就会将无限等待的A线程唤醒。注意的是唤醒,如果获取到锁对象,那么A线程就进入Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)

Waiting线程状态图

【Java基础进阶笔记】- Day06 - 第三章 线程状态Java基础进阶笔记 - Day06 - 第三章 线程状态

3.5 补充知识点

到此我们对于线程状态有了基本认识,想要了解更多,可见下图

【Java基础进阶笔记】- Day06 - 第三章 线程状态Java基础进阶笔记 - Day06 - 第三章 线程状态

小贴士:

我们在翻阅API的时候发现Timed_Waiting(计时等待)与Waiting(无限等待)状态联系还是很紧密的,比如Waiting(无限等待)状态中wait方法时空参的,而Timed_Waiting(计时等待)中wait方法是带参的。这种带参的方法,其实是一种倒计时操作,相当于我们生活中的小闹钟,我们约定好时间,到时通知,可是如果提前得到(唤醒)通知,那么设定好时间再通知就显得多此一举了,那么这种设计方案其实是一举两得。如果没有得到(唤醒)通知,那么线程就处于Timed_Waiting状态,直到倒计时完毕自动醒来,如果在倒计时期间得到(唤醒)通知,那么线程从Timed_Waiting状态立刻醒来

继续阅读