Java线程状态

网上流传了很久的线程具备5种状态,这样是不贴切JDK中描述的,JDK中描述线程状态只有6种,而网络流传的5种
状态就是进程的五态模型。那张广为流传的来自网络的图如下:

1542897157060

很明显这是操作系统中进程的5种状态,在很多操作系统书中也由介绍分别为new,ready,running,waiting,
terminated。不幸的是,有很多的书上常常把这些进程状态,线程状态与Java线程状态混在一起谈。

进程与线程的区分总图:

1542897357723

  1. 很多人觉得在JVM线程中应该有,Running运行状态。对JAVA而言,Runnable包含了就绪与运行,那为
    什么JAVA不区分开呢?这跟CPU分配的时间片有关,而且JAVA进行的是抢占式轮转调度,由于我们的JVM线程是服务于监控,线程又是切换的如此之快,那么区分ready与running又没有多大意义了。
    再者,我们都知道现在使用的很多JVM底层都将线程映射到操作系统上了,JVM本身没有做什么调度,因为虚拟机看到的都是底层的映射与封装,故而将ready与running映射来也没有太大意义,不如统一为Runnable
  2. 总之还是有些乱的,我们不妨就拿Windows系统为例,用的就是“进程”和“线程”这两种较为标准的叫法,
    这时一个进程下至少有一个线程,线程是CPU调度的基本单位进程不参与CPU调度,CPU根本不知道进程的存在。
  3. 为了避免混乱,下面说的线程状态,只是站在JVM层面上

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

1543039901810

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒
Timed Waiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
  1. 初始(NEW):新创建了一个线程对象,但还没有调用 start() 方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)
  3. 阻塞(BLOCKED):表示线程阻塞于锁
  4. 等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)
  5. 超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回
  6. 终止(TERMINATED):表示该线程已经执行完毕

1. NEW(新建)状态

概念:线程对象创建了,但是还没有启动之前,就是新建状态

实现 Runnable接口 或继承 Thread 可以得到一个线程类,new一个实例出来,线程就进入了初始状态。

这里强调两点:

  • 线程对象创建之后,还未开启( 调用start()方法 )时候,就处于NEW的状态
  • 开启线程,指的是调用start方法,并不是run方法,run方法仅仅作为一个普通方法存在

线程对象调用 run() 方法不开启线程,仅是对象调用方法。线程对象调用 start() 方法开启线程,并让jvm调用 run() 方法在开启的线程中执行

当我们执行 new Thread(target) 时,jvm 要为线程的运行做一些前期的准备工作,比如检查线程类是否已经被加载、解析和初始化过,接下来还要为对象分配空间并对空间初始化零值等。当完成这些准备工作时线程才能进入到下一个 Runnable (可运行)状态。

在我们研究线程状态时,采用Thread中的getState()方法进行研究

通过代码演示:

public class MyThread extends Thread{

    @Override
    public void run() {
        // 线程执行的代码
    }
}
/**
 * 研究新建线程状态
 *   新建状态:至今还未启动的线程处于这一状态
 */
public class ThreadStateTest1 {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread myThread = new MyThread();
        // 线程默认名:Thread-0 Thread-1...
        System.out.println(myThread.getName());
        System.out.println("线程创建之后处于:" + myThread.getState());

        myThread.run();
        System.out.println("线程run之后处于:" + myThread.getState());

        myThread.start();
        System.out.println("线程start之后处于:" + myThread.getState());

    }
}
// 输出:
//   线程创建之后处于:NEW
//   线程run之后处于:NEW
//   线程start之后处于:RUNNABLE

当我们执行 new Thread(target) 时,jvm 要为线程的运行做一些前期的准备工作,比如检查线程类是否已经被加载、解析和初始化过,接下来还要为对象分配空间并对空间初始化零值等。当完成这些准备工作时线程才能进入到下一个 Runnable (可运行)状态。所以说

当业务需要频繁创建线城市,最好使用线程池,提高效率减轻JVM的压力。当然如果大量线程进行频繁上下文切换,此时多线程的效率会大打折扣。

2. RUNNABLE(可运行)状态

可运行状态:一个在JVM中执行的线程处于这个状态中,等待JVM调度,德能在执行,也可能在等待

注:这里的等待指的是等待调度,等待的是系统资源,如IO、CPU时间片,与 sleep、lock 的等待有着本质差别。

接下来使用代码演示一个最简单的可运行状态:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程开始执行");
        System.out.println("线程开始执行具体的任务");
        // 假设这个任务使用5秒钟
        long beiginTime = System.currentTimeMillis();
        while (System.currentTimeMillis()-beiginTime < 5000){
            // 假设做了5秒钟的任务
        }
        System.out.println("线程执行完毕");
    }
}
/**
 * 可运行状态
 *   当线程有资格运行,调用了start方法,线程首先进入可运行状态
 *   这种可运行状态不一定被线程调度运行
 *   简单来说,调用start方法之后,该线程处于可运行状态,但未运行
 *   此时存放在"可运行池"中
 *   线程在运行的过程中,自然该线程也是处于可运行状态
 *
 *   JDK中处于可运行状态的线程,有两种,一种是正在JVM中运行,
 *   另一种是可能正在等待操作系统其它资源,比如处理器
 */
public class Demo {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread myThread = new MyThread();
        System.out.println("创建完成之后:" + myThread.getState());

        // 开启线程
        myThread.start();
        System.out.println("开启线程之后:" + myThread.getState());
    }
}

3. BLOCHED(阻塞)状态

我们还是使用代码来解析一下锁阻塞状态:

public class ThreadA extends Thread {
    private Object obj;

    public ThreadA(String name,Object obj){
        super(name);
        this.obj = obj;
    }

    @Override
    public void run() {
        // 同步代码块
        synchronized (obj){
            System.out.println("线程A开始执行");
            System.out.println("线程A真正开始执行代码了");
            long beginTime = System.currentTimeMillis();
            // 模拟5秒钟的任务
            while(System.currentTimeMillis()-beginTime < 5000){
            }

            System.out.println("线程A执行完毕");
        }
    }
}
public class ThreadB extends Thread{
    private Object obj;

    public ThreadB(String name,Object obj){
        super(name);
        this.obj = obj;
    }

    @Override
    public void run() {
        // 同步代码块
        synchronized (obj){
            System.out.println("线程B开始执行");
            System.out.println("线程B真正开始执行代码了");
            long beginTime = System.currentTimeMillis();
            // 模拟5秒钟的任务
            while(System.currentTimeMillis()-beginTime < 5000){
            }

            System.out.println("线程B执行完毕");
        }
    }
}
/**
 * 线程状态之阻塞状态BLOCKED
 *    JDK:锁阻塞并且正在等待监视器锁的某一线程状态
 *    处于受阻状态的某一线程正在等待监视器锁,以便进入一个同步代码块/同步方
 *    还有就是 调用Object.wart方法之后,再次进入同步中时
 *
 */
public class BlockedDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个锁对象
        Object obj = new Object();

        // 创建线程A、B
        ThreadA a = new ThreadA("线程A", obj);
        ThreadB b = new ThreadB("线程B", obj);

        // 开启线程
        a.start();
        b.start();

        //
        Thread.sleep(3000);
        System.out.println("线程A的状态是:" + a.getState());
        System.out.println("线程B的状态是:" + b.getState());

        //
        Thread.sleep(3000);
        System.out.println("线程A的状态是:" + a.getState());
        System.out.println("线程B的状态是:" + b.getState());
    }
}

在这里我们只是对之前分析的情况一进行了阐释

4. Timed Waiting(计时等待)状态

带指定的等待时间的等待线程所处的状态。一个线程处于这一状态是因为用一个指定的正的等待时间(为参数)调用了一下方法中的其一:

  • Thread.sleep
  • 带时限(timeout)的 Object.wait
  • 带时限(timeout)的 Thread.join
  • LockSupport.parkNanos
  • LockSupport.parkUntil

Timed Waiting 在API中的描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态。单独的去理解这句话,真是玄之又玄,其实我们在之前的操作中已经接触过这个状态了,在哪里呢?

在我们写卖票的案例中,为了减少线程执行太快,现象不明显等问题,我们在run方法中添加了sleep语句,这样就强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。

注:sleep的使用时区别去其他方法的。

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

实现一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串

代码:

/**
 * 限时等待
 */
public class MyThread extends Thread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("mt的线程状态: " + myThread.getState());

        }
    }

    // 实现一个计算器,0-99计数,在每个数字之间暂停1秒,每个10个数字输出一个字符串

    @Override
    public void run() {
        for (int i = 0; i < 99; i++) {
            if (i%10 == 0){
                System.out.println("oewewowllskd " + i);
            }

            System.out.println(i);
            try {
                Thread.sleep(1000);
                System.out.println("    休息了1秒");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

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

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

小提示:sleep() 中指定的时间是线程不会运行的最短时间。因此,sleep() 方法不能保证该线程睡眠到期后就开始立刻执行

5. WAITING(无限等待)状态

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

一个线程进入 WAITING 状态是因为调用了以下方法:

  • 不带时限的 Object.wait 方法
  • 不带时限的 Thread.join 方法
  • LockSupport.park

然后会等其他线程执行一个特别的动作,比如:

  • 一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的 Object.notify() 或 Object.notifyAll()
  • 一个调用了 Thread.join 方法的线程会等待指定的线程结束

代码演示:

/**
 * 无限等待
 *   Object中的wait方法完成
 *     使用当前线程 进入无限等待状态,直到其他线程有唤醒 notify 或 notifyAll 才能被唤醒
 *
 *     线程间通信 两个线程执行不同的操作 关联的
 *       两个线程 使用同样的锁 只能使用锁对象调用wait方法或者notify方法
 */
public class WaitingTest {
    private static Object obj = new Object();
    public static void main(String[] args) throws InterruptedException {
        // 使用匿名函数创建线程
        Thread t1 = new Thread() {
            @Override
            public void run() {
                synchronized (obj){
                    System.out.println("获取到锁,调用wait方法,当前线程进入无线等待状态。。。等待着别的线程来唤醒");
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("唤醒了这个线程,就不再是线程等待了,线程执行完毕");
                }
            }
        };
        // 开启线程t1
        t1.start();

        // 使用匿名内部内方式创建一个新的线程,用来唤醒t1线程
        new Thread(){
            @Override
            public void run() {
                // 获取到锁
                synchronized (obj){
                    try {
                        // 3秒钟后执行唤醒操作
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("获取到锁 执行 唤醒操作");

                    // 唤醒操作
                    obj.notify();
                }
            }
        }.start();

        // 4秒后查看线程t1状态
        Thread.sleep(4000);
        System.out.println("查看t1的线程状态" + t1.getState());
    }
}

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

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

6. TEMINATED(终止)状态

线程因如下两个原因之一将被终止:

  1. run() 方法正常退出而自然死亡
  2. 一个没有捕获的异常终止了 run() 方法而意外死亡

线程的方法

1. wait(), notify(), notifyAll()等方法介绍

在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait() 的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify() 是唤醒单个线程notifyAll() 是唤醒所有的线程

Object类中关于等待/唤醒的API详细信息如下:
notify() – 唤醒在此对象监视器上等待的单个线程。
notifyAll() – 唤醒在此对象监视器上等待的所有线程。
wait() – 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) – 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) – 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。