1.1.4 线程的状态

Java 并发编程的艺术第2版学习笔记

原书:《Java 并发编程的艺术第2版》 | 作者:方腾飞 魏鹏 程晓明 | 2023 年 9 月 | 机械工业出版社

Java 线程的状态

状态名称说明
NEW初始状态,线程被创建,但还没有调用 start() 方法
RUNNABLE运行状态,Java 线程将操作系统中的就绪运行两种状态笼统的称作运行中
BLOCKED阻塞状态,表示线程阻塞于锁
WAITING等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知中断
TIMED_WAITING超时等待状态,该状态不同于 WAITING,它可以在指定的时间自行返回
TERMINATED终止状态,表示当前线程已经执行完毕

示例代码

ThreadState

 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
38
39
40
41
42
43
44
45
46
47
48
49
50
class ThreadState {

    public static void main(String[] args) {
        new Thread(new TimeWaiting(), "TimeWaitingThread").start();
        new Thread(new Waiting(), "WaitingThread").start();
        // 使用两个 Blocked 线程,一个获取锁成功,另一个被阻塞
        new Thread(new Blocked(), "BlockedThread-1").start();
        new Thread(new Blocked(), "BlockedThread-2").start();
    }

    // 该线程不断的睡眠
    static class TimeWaiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                sleep100Seconds();
            }
        }
    }

    // 该线程在 Waiting.class 实例上等待
    static class Waiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    // 该线程在 Blocked.class 实例上加锁后,不会释放该锁
    static class Blocked implements Runnable {
        @Override
        public void run() {
            synchronized (Blocked.class) {
                sleep100Seconds();
            }
        }
    }

    private static void sleep100Seconds() {
        SleepUtils.second(100);
    }
}

SleepUtils

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import java.util.concurrent.TimeUnit;

class SleepUtils {
    static void second(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行 ThreadState 后,可以通过 jps 命令查看 Java 进程号,然后通过 jstack 进程号 查看线程状态。

使用 jps 命令查看当前运行的 Java 进程
使用 jstack 命令查看线程堆栈信息,需要快速敲命令,不然 BlockedThread-1 就挂了

AI 分析结果

  • TimeWaitingThread (线程 14): 处于 TIMED_WAITING (sleeping) 状态。 这意味着该线程正在调用 sleep 方法并等待一段时间。
  • WaitingThread (线程 15): 处于 WAITING (on object monitor) 状态。 这意味着该线程正在等待某个对象的监视器 (monitor) 被释放。
  • BlockedThread-1 (16): 同样处于TIMED_WAITING状态,也是因为调用了Thread.sleep()。
  • BlockedThread-2 (线程 17): 处于 TIMED_WAITING (sleeping) 状态。 这意味着该线程正在调用 sleep 方法并等待一段时间。

注意

  • Java 将操作系统中的就绪运行两种状态笼统的称作运行状态
  • 阻塞状态是线程阻塞进入 synchronized 关键字修饰的方法或代码块「获取锁」时的状态
  • 但是阻塞在 java.concurrent 包中 Lock 接口的线程状态是 WAITING,而不是 BLOCKED,因为 java.concurrent 包中 Lock 接口对于阻塞的实现使用了 LockSupport 类中的相关方法

WAITING、BLOCKED 状态有什么区别?AI 回答

WAITING 状态

  • 线程进入 WAITING 状态通常是因为调用了 Object.wait() 方法。
  • 处于 WAITING 状态的线程会释放锁,并进入等待队列。
  • 只有当其他线程调用 Object.notify() 或 Object.notifyAll() 方法唤醒等待队列中的线程时,WAITING 状态的线程才会被唤醒。
  • WAITING 状态通常用于线程之间的同步和协作。

BLOCKED 状态

  • 线程进入 BLOCKED 状态通常是因为尝试获取锁失败。
  • 处于 BLOCKED 状态的线程会一直等待锁的释放,不会释放锁
  • 当锁被释放时,处于 BLOCKED 状态的线程会自动竞争锁,并有可能进入 RUNNING 状态。
  • BLOCKED 状态通常用于防止多个线程同时进入临界区。

WAITING、BLOCKED 优缺点?AI 回答

WAITING

特点

  • 等待通知:当线程调用 Object.wait()、Thread.join() 或 LockSupport.park() 方法时,会进入 WAITING 状态。此时,线程会放弃当前持有的所有锁,并进入无限期等待,直到其他线程通过调用 Object.notify()、Object.notifyAll() 或 LockSupport.unpark() 对其进行显式唤醒,或者线程被中断。
  • 被动等待:处于 WAITING 状态的线程不参与任何锁的竞争,而是完全依赖于其他线程的操作来恢复执行。线程在等待期间不会消耗 CPU 资源,而是被操作系统挂起。

优点

  • 资源节约:线程处于 WAITING 状态时,不会占用处理器时间,有利于减少系统开销,提高资源利用率。
  • 协作机制:通过等待通知机制,线程间可以实现高效的协作和同步,例如生产者消费者模式、线程间的依赖等待等。

缺点

  • 依赖外部唤醒:线程何时恢复执行完全取决于其他线程是否正确发送唤醒信号,若没有正确的通知机制或因编程错误导致唤醒信号丢失,可能会导致线程长期等待甚至死锁。
  • 中断响应:虽然可以通过中断请求来唤醒 WAITING 线程,但在实际操作中可能需要额外的逻辑来处理中断请求,以确保线程能够正确响应中断并清理资源。

BLOCKED

特点

  • 锁竞争:线程在试图获取一个由其他线程持有的监视器锁(如通过 synchronized 关键字修饰的代码块或方法)时,如果锁不可用,线程会进入 BLOCKED 状态。线程会等待锁的释放,一旦持有锁的线程释放锁,操作系统会选择一个合适的 BLOCKED 线程将其转换为就绪状态。
  • 主动阻塞:线程处于 BLOCKED 状态是因为它在积极尝试获取锁资源,而并非主动放弃控制权进入等待。线程在等待锁的过程中可能会频繁切换,增加上下文切换的开销。

优点

  • 互斥与同步:通过 BLOCKED 状态,Java 实现了线程间的互斥访问,确保同一时刻只有一个线程能访问临界区资源,避免数据竞争和一致性问题。
  • 简化编程:使用 synchronized 等内置锁机制,使得并发编程相对简单,无需手动管理锁的获取与释放。

缺点

  • 性能影响:频繁的锁竞争可能导致大量的上下文切换和CPU缓存失效,降低系统性能。特别是在锁粒度过粗或者锁争用激烈的情况下,可能成为性能瓶颈。
  • 死锁风险:不当的锁顺序或嵌套锁使用可能导致死锁,即多个线程互相等待对方持有的锁,形成僵局,所有线程都无法继续执行。
comments powered by Disqus