# 线程的状态及转换

# 状态简介

Java 中线程一共初始状态 (NEW)、运行状态 (RUNNABLE)、阻塞状态 (BLOCKED)、等待状态 (WAITING)、超时等待状态 (TIMED_WAITING)、终止状态 (TERMINATED)有六种状态。

初始状态 (NEW) :尚未启动的线程处于此状态。通常是新创建了线程,但还没有调用 start () 方法。

运行状态 (RUNNABLE):Java 线程中将就绪(ready)和运行中(running)两种状态笼统的称为 "运行中"。比如说线程可运行线程池中,等待被调度选中,获取 CPU 的使用权,此时处于就绪状态(ready)。

就绪状态的线程在获得 CPU 时间片后变为运行中状态(running)。

等待状态 (WAITING):进入该状态的线程不会被分配 CPU 执行,需要等待其他线程做出一些特定动作(唤醒或中断)。

超时等待状态 (TIMED_WAITING):进入该状态的线程需要等待其他线程在指定时间内做出一些特定动作(唤醒或中断),可以在指定的时间自行返回。

阻塞状态 (BLOCKED):表示线程阻塞于锁,等待排他锁。

终止状态 (TERMINATED):表示该线程已经执行完毕,已退出的线程处理此状态,被终止的线程不能再次被启动。

如果再次启动会抛出 java.lang.IllegalThreadStateException 异常。

# 状态变化

# NEW -> RUNNABLE

线程创建之后它将处于 NEW 状态。

调用 start() 方法后开始运行,线程这时候处于 READY 状态。可运行状态的线程获得了 CPU 时间片后就处于 RUNNING 状态。

Thread1 thread1 = new Thread1();
thread1.start();
1
2

# RUNNABLE -> WAITING

没有设置 timeoutObject.wait() 方法。

没有设置 timeoutThread.join() 方法。

LockSupport.park() 方法。

以上三种方式会让线程进入到无限期等待状态

// 定义锁对象
final Object o = new Object();

// 创建一个线程
Thread thread1 = new Thread(() -> {
  synchronized (o){
    try {
      System.out.println(Thread.currentThread().getName() + " Start...");
      o.wait(100);
      System.out.println(Thread.currentThread().getName() + " End...");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# RUNNABLE -> TIME_WAITING

Thread.sleep() 方法。

设置了 timeout 参数的 Object.wait(timeout) 方法。

设置了 timeout 参数的 Thread.join(timeout) 方法。

LockSupport.parkNanos() 方法。

LockSupport.parkUntil() 方法。

# WAITING/TIME_WAITING -> RUNNABLE

Object.notify() 方法。

Object.notifyAll() 方法。

LockSupport.unpark(Thread) 方法。

# RUNNABLE -> BLOCKED

等待进入 synchronize 方法快。

等待获取被 synchronize 锁住的资源对象。

# 主要方法介绍

# start() 和 run()

注意:这里常会问到是否可以直接调用 run() 方法。

通过下面的代码可以看出,使用 run() 方法的执行过程,其实并没有创建新的线程,而是直接用主线程直接执行,属于方法调用。

public class StartAndRun {
    public static void main(String[] args) {
        System.out.println("主线程名称:" + Thread.currentThread().getName());

        Thread1 thread1 = new Thread1();
        // thread1.start();
        thread1.run();
    }
}

class Thread1 extends Thread {
    @Override
    public void run() {
        System.out.println("新线程名称:" + Thread.currentThread().getName());
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

使用 start() 输出的结果是

主线程名称:main
新线程名称:Thread-0
1
2

使用 run() 输出的结果是

主线程名称:main
新线程名称:main
1
2

# wait() 和 sleep()

相同点:

sleep() 方法和 wait() 方法均可以使线程进入等待,但其处理方式有本质的不同。

不同点

  1. sleep() 方法是 Thread 类中的方法,wait() 方法是 Object 类中定义的方法。
  2. sleep() 方法可以在任意地方使用。
  3. wait() 方法只能在 synchronize 方法或 synchronize 代码块中使用。
  4. Thread.sleep() 只会让出 CPU,并不会让出
  5. Object.wait() 不仅会让出 CPU,还会释放已经占有的同步资源锁。

使用 wait() 方法执行的情况:

public static void main(String[] args) {
  System.out.println("主线程名称 start...");

  final Object o = new Object();

  // 创建一个线程
  Thread thread1 = new Thread(() -> {
    synchronized (o){
      try {
        System.out.println("子线程1 Start...");
        o.wait(10);
        System.out.println("子线程1 End...");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  });

  // 创建第二个线程
  Thread thread2 = new Thread(() -> {
    synchronized (o) {
      try {
        System.out.println("子线程2 Start...");
        Thread.sleep(10);
        System.out.println("子线程2 End...");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  });

  // 启动线程
  thread1.start();
  thread2.start();

  System.out.println("主线程名称 end...");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

输出信息如下

主线程名称 start...
主线程名称 end...
子线程1 Start...
子线程2 Start...
子线程2 End...
子线程1 End...
1
2
3
4
5
6

如果线程一种的 o.wait(10) 改为 Thread.sleep(10) 输出的信息如下

主线程名称 start...
主线程名称 end...
子线程1 Start...
子线程1 End...
子线程2 Start...
子线程2 End...
1
2
3
4
5
6

# join()

join() 也可以使线程进入等待状态,但它和 wait() 以及 sleep() 的本质区别是,调用 join() 后进入等待的是主线程,而不是当前线程。

public static void main(String[] args) throws InterruptedException {
  System.out.println("主线程 start...");
  Thread thread3 = new Thread(() -> {
    System.out.println("子线程:start");
    try {
      Thread.sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("子线程:end");
  });

  thread3.start();
  thread3.join();
  System.out.println("主线程 end");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

上方代码输出的结果是:

主线程:start...
子线程:start
子线程:end
主线程:end
1
2
3
4

如果注释掉 thread3.join() 输出的结果则变成了

主线程:start...
主线程:end
子线程:start
子线程:end
1
2
3
4

由此可以看出,join() 方法会使主线程进入等待状态。

# yield()

yield() 意思是当前线程做出让步,放弃当前 cpu,让 cpu 重新选择线程,避免线程过度使用 cpu.

有点需要说明的是,让步不是绝不执行,重新竞争时,cpu 也有可能重新选中自己。

示例代码如下:

public class YieldTest {

    public static void main(String[] args) {

        Thread thread1 = new YieldThread("t1");
        Thread thread2 = new YieldThread("t2");

        thread1.start();
        thread2.start();
    }

}

class YieldThread extends Thread{

    public YieldThread(final String threadName) {
        super(threadName);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if ("t1".equals(getName())) {
                Thread.yield();
            }
            System.out.println(getName() + "执行次数: " + i);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

根据上方代码, 我们虽然每次执行到线程 t1 的时候都会 yield() 但是输出的结果并非是先让 t2 执行完, t1 再执行.

# notify() 和 notifyAll()

notify() 方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll() 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用 notifyAll() 方法。

示例代码如下:

public static void main(String[] args) {

  final Object lock = new Object();

  Thread thread1 = new Thread(() -> {
    synchronized (lock) {
      for (int i = 0; i < 4; i++) {
        try {
          if (i == 2) {
            lock.wait();
          }
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": " + i);
      }
    }
  });
  thread1.setName("t1");

  Thread thread2 = new Thread(() -> {
    synchronized (lock) {
      for (int i = 0; i < 4; i++) {
        System.out.println(Thread.currentThread().getName() + ": " + i);
      }
      lock.notify();
    }
  });
  thread2.setName("t2");

  thread1.start();
  thread2.start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

其输出结果是:

t1: 0
t1: 1
t2: 0
t2: 1
t2: 2
t2: 3
t1: 2
t1: 3
1
2
3
4
5
6
7
8

从打印的结果我们可以看出, thread1 执行两次后进入了 WAITING 状态, 当 thread2 对锁对象调用 notify() 方法后, thread1 又一次进入了执行.

上次更新时间: 2020/11/3 上午12:34:40