线程是操作系统中能够进行运算调度的最小单位了。它被包含在进程中,是进程在运行过程中,真正的『打工人』。每一个进程有且至少有一个线程存在,默认的线程我们可以把它叫作『主线程』。

在 Java 中,进行线程的切换是一件比较浪费资料的事情,这意味着代码的执行需要从用户态切换到内核态,然后再从内核态切换回用户态。但这也不是说我们不能用线程,合理的使用可以提高程序执行的吞吐率。

既然上面提到了『线程切换』,那我们就来聊聊,线程的几种状态。

线程的六种状态

根据官方文档来看,Java 中线程有六种状态,分别是 NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED

NEW

刚创建线程对象时,线程就处在这个状态。该状态只能向下一个状态 RUNNABLE 发生转换,一旦发生转换,则无法再回到 NEW 状态。

比如下方代码:

Runnable runnable = new Runnable() {
    @Override
    public void run() { }
};
Thread thread = new Thread(runnable);
System.out.println("t1 state: " + thread.getState());

则会看到输出:

t1 state: NEW

RUNNABLE

当应用程序调用了线程对象的 start() 方法之后,该线程就会进入 RUNNABLE 状态。此时,该线程会被置于可运行的线程池中,等待被线程调度选中,获取 CPU 的使用权。一旦线程获取到 CPU 的使用权,则开始真正执行程序代码。我们还可以将这个状态细分为 READYRUNNING 两种状态,用于区分线程是否抢占到 CPU,已经开始运行代码了。

Runnable runnable = new Runnable() {
    @Override
    public void run() { }
};
Thread thread = new Thread(runnable);
thread.start();
System.out.println("t1 state: " + thread.getState());

则会输出:

t1 state: RUNNABLE

BLOCKED

在这种状态下,线程一般是在等待 monitor 的锁。一般我们管这种状态叫『阻塞状态』。进入阻塞状态是因为某种原因下,线程放弃了 CPU 的使用权,线程暂停执行代码,进入阻塞状态。

用下面的例子来说明:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new DemoThreadB());
        Thread t2 = new Thread(new DemoThreadB());

        t1.start();
        t2.start();

        Thread.sleep(1000);

        System.out.println("t2 state: " + t2.getState());
        System.exit(0);
    }

    static class DemoThreadB implements Runnable {
        @Override
        public void run() {
            process();
        }

        public static synchronized void process() {
            while(true) {
                // 模拟重量级的过程处理,t1 如果正在运行该代码块,则 t2 就会进入 BLOCKED 状态
            }
        }
    }
}

这个例子中创建了 t1 和 t2 两个线程。t1 在调用 start() 方法后进入 process() 方法。因为这里使用了 while(true) 来保证线程不会轻易狗带,所以 t2 就甭想抢占到 CPU 并执行 process() 方法。但 t2 也调用了 start() 方法进入了 RUNNABLE 状态,又由于比 t1 晚了一步,所以只能进入 BLOCKED 状态,等待 t1 释放锁。

该例子会输出:

t2 state: BLOCKED

WAITING

在这种状态下,线程就是在无限期等待其他线程完成某个操作。比如说,某个线程在一个对象上调用了 Object.wait(),就是在等待另一个线程对该对象调用 Object.notifiy() 或者 Object.nofityAll()。有可能引起线程进入 WATING 状态的方法有下面几种:
- Object.wait() 无超时的等待
- Thread.join() 无超时的等待
- LockSupport.park()

我们使用下面的例子来模拟这个状态:

public class Main {

    static Thread t1;

    public static void main(String[] args) throws InterruptedException {

        Thread t2 = new Thread(new DemoThreadB());
        t1 = new Thread(new DemoThreadA(t2));
        t1.start();
    }

    static class DemoThreadA implements Runnable {
        Thread t2;

        DemoThreadA(Thread thread) {
            this.t2 = thread;
        }

        @Override
        public void run() {
            this.t2.start();

            try {
                this.t2.join();
                System.out.println("t1 state: " + t1.getState());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class DemoThreadB implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("t1 state: " + t1.getState());
        }
    }
}

上面的例子中,创建了两个线程 t1 和 t2,并且在 t1 的 run() 方法中启动了 t2,并且调用了 t2 的 join() 方法。此时,t1 就进入了 WAITING 状态,当 t2 的 run() 方法执行完毕后,t1 就会从 WAIT 状态回到 RUNNABLE 状态。

所以上述代码的执行结果如下:

t1 state: WAITING
t1 state: RUNNABLE

TIMED_WAITING

顾名思义,这种状态也是 WAITNG 状态,只不过这种状态是有等待时限的。有可能引起线程进入 TIMED_WAITING 状态的方法有下面几种:
- Thread.sleep(long millis) 不必介绍
- Object.wait(long timeout) 带有时限的 Object.wait()
- Thread.join(long millis) 带有时限的 Thread.join()
- LockSupport.parkNanos(Object blocker, long nanos)
- LockSupport.parkUntil(Object blocker, long deadline)

同样地,我们使用一个例子来演示这个状态:

public class Main {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new DemoThreadC());
        t1.start();

        Thread.sleep(1000);
        System.out.println("t1 state: " + t1.getState());
    }

    static class DemoThreadC implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
                // Thread.join(3000);
                // LockSupport.parkNanos(this, 3000000000);
                // LockSupport.parkUntil(this, 3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

主线程 sleep 1 秒就结束了,而子线程 t1 则由于『业务量巨大』导致 sleep 3 秒后才能退出,在 1 秒之后观察 t1 的状态,则输出如下:

t1 state: TIMED_WAITING

TERMINATED

线程的 run() 方法执行完毕,或者因为异常导致退出了 run() 方法,则该线程的生命周期就结束了,进入 TERMINATED 状态。进入 TERMINATED 状态的线程不可再次重生。

这个例子就简单多了:

Runnable runnable = new Runnable() {
    @Override
    public void run() { }
};
Thread t1 = new Thread(runnable);
t1.start();

Thread.sleep(1000);
System.out.println("t1 state: " + t1.getState());

线程瞬间运行完毕(因为 run() 方法为空),1秒后观察 t1 的状态,就会有如下输出:

t1 state: TERMINATED

下面用一张图来展示这六种状态之间的关系:

线程的六种状态转换

按时间顺序来解释一下:

  • NEW 状态下的线程,在调用 start() 方法之后,进入 RUNNABLE 状态。此时可以细分为 READYRUNNING 两种状态,READY 状态下还未抢占到 CPU,代码无法执行;抢占到 CPU,则进入 RUNNING 状态,开始执行代码;
  • 如果在 RUNNABLE 状态下调用了 Object.wait() 或者 Thread.join() 或者 LockSupport.park(),则进入 WAIT 状态;
  • 如果在 RUNNABLE 状态下调用了 Thread.sleep(long millis) 或者 Object.wait(long timeout) 或者 Thread.join(long millis) 或者 LockSupport.parkNanos(Object locker, long nanos) 或者 LockSupport.parkUntil(Object blocker, long deadline),则进入 TIMED_WAITING 状态;
  • 如果在 RUNNABLE 状态下还需要等待 monitor 锁进入同步代码块或者重入同步代码块,则进入 BLOCKED 状态;
  • WAITING 或者 TIMED_WAITING 状态下,如果其他线程调用了 Object.notify() 或者 Object.nofityAll() 方法,则进入 RUNNABLE 状态,
  • BLOCKED 状态下,一旦获取 monitor 锁,则回到 RUNNABLE 状态;
  • WAITING 或者 TIMED_WAITING 或者 BLOCKED 状态下,线程代码执行结束,或因为异常提前结束,都会进入 TERMINATED 状态。