头图
stateDiagram-v2
    [*] --> NEW: 创建线程对象
    NEW --> RUNNABLE: 调用start()
    RUNNABLE --> BLOCKED: 等待synchronized锁
    BLOCKED --> RUNNABLE: 获得锁
    RUNNABLE --> WAITING: 调用wait()/join()/park()
    WAITING --> RUNNABLE: 调用notify()/notifyAll()/unpark()或目标线程结束
    RUNNABLE --> TIMED_WAITING: 调用sleep(time)/wait(time)/join(time)
    TIMED_WAITING --> RUNNABLE: 时间到期或被中断
    RUNNABLE --> TERMINATED: 运行结束
    TERMINATED --> [*]

引言

各位开发者好!在上一篇文章中,我们详细介绍了 Java 多线程的四种创建方式。今天,我们将深入探讨线程的生命周期和基础操作方法,这些知识对于理解多线程程序的行为和调试线程问题至关重要。

很多初学者在多线程编程中遇到的困惑,往往源于对线程状态转换和控制方法的理解不足。比如:为什么我的线程没有执行?为什么线程无法停止?如何优雅地结束一个线程?今天,我们就来一一解答这些问题。

一、线程的六大状态详解

Java 中的 Thread 类定义了线程的六种状态,这些状态定义在 Thread 类的内部枚举 State 中:

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

下面我们详细解析每一种状态及其转换过程:

1. NEW(新建)

当你创建一个 Thread 对象但还没有调用 start()方法时,线程处于 NEW 状态。

Thread thread = new Thread(() -> {
    System.out.println("线程任务执行");
});
thread.setName("worker-thread"); // 给线程起一个有意义的名称
System.out.println("创建后线程状态:" + thread.getState()); // 输出: NEW

2. RUNNABLE(可运行)

调用 start()方法后,线程进入 RUNNABLE 状态。在 Java 中,RUNNABLE 状态包含了操作系统层面的"就绪"和"运行中"两个状态:

  • 就绪:线程已经准备好运行,但等待 CPU 分配时间片
  • 运行中:线程正在 CPU 上执行

重要说明:Java 中的 RUNNABLE 状态是一个复合状态,不区分线程是"就绪"还是"正在运行",这与操作系统的线程状态模型不同。即使线程获得了 CPU 时间片正在执行,在 Java API 看来它仍然是 RUNNABLE 状态,无法通过 Thread.getState()区分线程是否正在 CPU 上执行。

thread.start();
System.out.println("启动后线程状态:" + thread.getState()); // 输出: RUNNABLE
graph TD
    A[操作系统线程状态] --> B[就绪Ready]
    A --> C[运行中Running]
    A --> D[阻塞Blocked]
    B --> J[Java RUNNABLE]
    C --> J
    D --> K[Java BLOCKED/WAITING/TIMED_WAITING]

    style J fill:#9cf,stroke:#333,stroke-width:2px
    style K fill:#f9a,stroke:#333,stroke-width:2px

3. BLOCKED(阻塞)

线程被阻塞,等待获取一个 synchronized 内置锁(也称 monitor 锁)。当线程尝试进入一个 synchronized 块/方法,但该锁被其他线程持有时,就会进入这个状态。

Object lock = new Object();

Thread thread1 = new Thread(() -> {
    synchronized (lock) {
        try {
            Thread.sleep(3000); // 持有锁3秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
thread1.setName("lock-holder");

Thread thread2 = new Thread(() -> {
    synchronized (lock) {
        System.out.println("线程2获取到了锁");
    }
});
thread2.setName("lock-waiter");

thread1.start(); // 先启动线程1
Thread.sleep(100); // 确保线程1先获取到锁
thread2.start(); // 再启动线程2
Thread.sleep(100); // 给线程2一点时间尝试获取锁

System.out.println("线程2状态:" + thread2.getState()); // 输出: BLOCKED

4. WAITING(等待)

线程进入无限期等待状态,需要其他线程执行特定操作后才能继续。
主要由以下方法导致:

  • Object.wait()
  • Thread.join()
  • LockSupport.park()

特别说明:Object.wait()方法会释放持有的 monitor 锁(也就是 synchronized 锁),而 LockSupport.park()不会释放任何锁,这是一个重要区别。

性能考虑:在高并发环境中,过多线程进入 WAITING 状态可能导致系统资源浪费。建议使用带超时参数的 wait(timeout),避免由于通知丢失导致线程永久等待。

Object lock = new Object();

Thread waitingThread = new Thread(() -> {
    synchronized (lock) {
        try {
            lock.wait(); // 进入WAITING状态,同时释放锁
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
waitingThread.setName("waiting-thread");

waitingThread.start();
Thread.sleep(100); // 确保waitingThread进入等待状态
System.out.println("等待中的线程状态:" + waitingThread.getState()); // 输出: WAITING

5. TIMED_WAITING(计时等待)

与 WAITING 类似,但有超时时间。以下方法会导致这个状态:

  • Thread.sleep(long)
  • Object.wait(long)
  • Thread.join(long)
  • LockSupport.parkNanos()
  • LockSupport.parkUntil()
Thread sleepingThread = new Thread(() -> {
    try {
        Thread.sleep(5000); // 休眠5秒,进入TIMED_WAITING状态
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
sleepingThread.setName("sleeping-thread");

sleepingThread.start();
Thread.sleep(100); // 确保sleepingThread进入休眠状态
System.out.println("休眠中的线程状态:" + sleepingThread.getState()); // 输出: TIMED_WAITING

6. TERMINATED(终止)

线程执行完毕或因异常结束。

Thread terminatedThread = new Thread(() -> {
    // 执行一些简短的任务
    System.out.println("任务执行完毕");
});
terminatedThread.setName("terminated-thread");

terminatedThread.start();
Thread.sleep(100); // 确保线程有足够时间完成任务
System.out.println("任务完成后线程状态:" + terminatedThread.getState()); // 输出: TERMINATED

三种等待状态的对比

状态触发条件是否释放锁恢复条件典型使用场景
BLOCKED等待进入 synchronized 同步块/方法不持有锁获得锁多线程竞争共享资源
WAITINGObject.wait()
Thread.join()
LockSupport.park()
wait()释放锁
join/park 不涉及锁释放
notify/notifyAll
目标线程结束
unpark
线程协作,等待条件满足
TIMED_WAITINGThread.sleep(time)
Object.wait(time)
Thread.join(time)
sleep 不释放锁
wait 释放锁
join 不涉及锁释放
时间到期或上述对应条件超时等待,避免无限阻塞
graph TD
    A[运行中的线程] -->|尝试获取被占用的synchronized锁| B[BLOCKED]
    A -->|"调用wait()"| C[WAITING]
    A -->|"调用sleep(time)"| D[TIMED_WAITING]
    B -->|获得锁| A
    C -->|"其他线程调用notify()"| A
    D -->|时间到期| A

    style A fill:#9cf,stroke:#333
    style B fill:#f9a,stroke:#333
    style C fill:#ffc,stroke:#333
    style D fill:#cfc,stroke:#333

二、start()与 run()的本质区别

初学者常犯的一个错误是直接调用线程的 run()方法,而不是 start()方法。这两者有本质区别:

调用 run()

Thread thread = new Thread(() -> {
    System.out.println("当前线程: " + Thread.currentThread().getName());
});
thread.run(); // 直接调用run方法

输出:当前线程: main

调用 start()

Thread thread = new Thread(() -> {
    System.out.println("当前线程: " + Thread.currentThread().getName());
});
thread.start(); // 调用start方法

输出:当前线程: Thread-0

区别分析

  • run(): 普通方法调用,在当前线程(通常是 main 线程)执行线程体,没有创建新线程
  • start(): 启动新线程,在新线程中执行 run()方法,实现了多线程并发执行

底层原理:start()方法会调用 native 方法 start0(),该方法会在 JVM 层面创建一个新的操作系统线程,并设置线程状态,最终导致 run()方法在新线程中执行。

graph TD
    A[Thread对象创建] --> B{调用方法?}
    B -->|"thread.run()"| C[在当前线程中执行run方法]
    B -->|"thread.start()"| D[创建新的操作系统线程]
    D --> E[在新线程中执行run方法]
    C --> F[无并发效果]
    E --> G[实现多线程并发]

    style C fill:#f9a,stroke:#333
    style E fill:#9cf,stroke:#333
    style F fill:#f9a,stroke:#333
    style G fill:#9cf,stroke:#333

常见错误案例

// 错误用法:重复调用start()
Thread thread = new Thread(() -> System.out.println("任务执行"));
thread.start();
thread.start(); // 抛出IllegalThreadStateException

// 错误理解:以为run()会启动线程
Thread thread2 = new Thread(() -> {
    for(int i=0; i<1000; i++) {
        System.out.println(i);
    }
});
thread2.run(); // 在主线程中顺序执行,没有并发效果

三、线程控制方法详解

Java 提供了几个重要的线程控制方法,下面我们来详细解析:

1. sleep() - 线程休眠

使当前线程暂停执行指定的时间,进入 TIMED_WAITING 状态,但不会释放锁。

public static void sleepDemo() {
    Object lock = new Object();

    Thread thread = new Thread(() -> {
        synchronized (lock) {
            System.out.println("线程获取到锁");
            try {
                System.out.println("线程开始休眠5秒");
                Thread.sleep(5000);
                System.out.println("线程休眠结束");
            } catch (InterruptedException e) {
                System.out.println("线程被中断");
            }
        }
    });
    thread.setName("sleeping-thread");

    thread.start();

    // 给点时间让线程启动并获取锁
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // 尝试获取锁,会被阻塞直到上面的线程释放锁
    synchronized (lock) {
        System.out.println("主线程获取到锁");
    }
}

要点

  • sleep 是 Thread 类的静态方法,会暂停当前正在执行的线程
  • 不会释放锁资源
  • 可以被 interrupt()方法中断,抛出 InterruptedException
  • sleep 结束后线程会自动回到 RUNNABLE 状态
  • 长时间的 sleep()会占用线程资源而不执行工作,在线程池环境中可能导致性能下降
  • 建议配合合理的超时机制使用,避免无限期阻塞

2. yield() - 线程让步

提示调度器当前线程愿意放弃 CPU 使用权,但调度器可以忽略这个提示。

public static void yieldDemo() {
    Thread thread1 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程1: " + i);
            if (i % 10 == 0) {
                System.out.println("线程1让步");
                Thread.yield();
            }
        }
    });
    thread1.setName("yield-thread");

    Thread thread2 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程2: " + i);
        }
    });
    thread2.setName("normal-thread");

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

要点

  • yield 是 Thread 类的静态方法
  • 只是提示调度器,没有强制性
  • 从运行状态到就绪状态的转变(仍然是 RUNNABLE)
  • 实际效果取决于操作系统的实现,不可靠

yield()在现代系统中的实际效果

尽管 yield()方法的理论目的是让出 CPU 时间片,但在现代操作系统和 JVM 实现中,它的实际效果往往不可预测:

  1. 大多数现代 CPU 调度器已经非常智能,能够有效分配时间片
  2. 不同 JVM 实现和操作系统对 yield()的处理方式不同
  3. 在某些系统上,yield()可能完全没有效果
  4. 在其他系统上,yield()可能导致当前线程被过度惩罚,长时间无法获得 CPU

在实际开发中

  • 避免使用 yield()来解决线程协作问题
  • 如需控制线程执行顺序,应使用显式同步机制(如 CountDownLatch、CyclicBarrier 等)
  • 如需控制执行时间分配,考虑使用线程优先级或更高级的调度框架

3. join() - 线程等待

让当前线程等待另一个线程执行完毕后再继续执行。

public static void joinDemo() {
    Thread worker = new Thread(() -> {
        System.out.println("工作线程开始执行...");
        try {
            // 模拟耗时操作
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("工作线程执行完毕");
    });
    worker.setName("worker-thread");

    System.out.println("主线程启动工作线程");
    worker.start();

    System.out.println("主线程等待工作线程完成");
    try {
        worker.join(); // 主线程在这里等待worker线程执行完毕
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("主线程继续执行");
}

join()方法的重载版本

  • join(): 等待线程终止
  • join(long millis): 等待指定的毫秒数
  • join(long millis, int nanos): 等待指定的毫秒数加纳秒数

join()方法的内部实现原理

// join()方法的内部实现原理(简化版)
public final synchronized void join(long millis) throws InterruptedException {
    if (millis > 0) {
        if (isAlive()) {
            // 当前线程在目标线程对象上等待
            wait(millis);
        }
    } else if (millis == 0) {
        while (isAlive()) {
            // 无限期等待
            wait(0);
        }
    }
    // 注意:当目标线程终止时,JVM会调用notifyAll()唤醒所有等待线程
}

深入理解:当线程 A 调用线程 B 的 join()方法时,线程 A 会在线程 B 对象的监视器上等待。当线程 B 执行完毕(无论正常结束还是异常结束),JVM 会调用线程 B 对象的 notifyAll()方法,从而唤醒在其上等待的线程 A。这种设计确保了线程 A 能够在线程 B 结束后继续执行。

性能考虑

  • 无限期的 join()可能导致调用线程长时间等待,降低系统吞吐量
  • 在线程池环境中,线程长时间阻塞会降低线程池效率
  • 推荐使用 join(timeout)设置合理的等待超时时间
sequenceDiagram
    participant 主线程
    participant 工作线程对象
    participant 工作线程

    主线程->>工作线程对象: 创建
    主线程->>工作线程: start()
    工作线程->>工作线程: 执行run()方法
    主线程->>工作线程对象: join()
    工作线程对象->>主线程: wait()
    Note over 主线程: 主线程进入WAITING状态
    工作线程->>工作线程: 完成任务
    Note over 工作线程: 进入TERMINATED状态
    工作线程->>工作线程对象: JVM调用notifyAll()
    工作线程对象->>主线程: 唤醒
    Note over 主线程: 继续执行

4. interrupt() - 线程中断

这是一种协作式的线程中断机制,用于通知线程应该停止或中断当前工作。

public static void interruptDemo() {
    Thread sleepingThread = new Thread(() -> {
        try {
            System.out.println("线程开始休眠10秒");
            Thread.sleep(10000);
            System.out.println("休眠完成"); // 如果被中断,这行不会执行
        } catch (InterruptedException e) {
            System.out.println("线程被中断: " + e.getMessage());
        } finally {
            System.out.println("线程结束");
        }
    });
    sleepingThread.setName("interrupted-thread");

    sleepingThread.start();

    // 主线程休眠2秒后中断sleepingThread
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("主线程发出中断请求");
    sleepingThread.interrupt();
}

中断机制相关方法

  • interrupt(): 请求中断线程
  • isInterrupted(): 检查线程是否被中断(不清除中断状态)
  • Thread.interrupted(): 静态方法,检查当前线程是否被中断(清除中断状态)

非阻塞场景的中断处理

在非阻塞状态下,interrupt()方法只会设置线程的中断标志,不会导致线程抛出 InterruptedException。此时,线程需要主动检查中断状态并作出响应:

public static void nonBlockingInterruptDemo() {
    Thread worker = new Thread(() -> {
        // 给线程起一个有意义的名称
        Thread.currentThread().setName("worker-thread");

        // 执行计算密集型任务,定期检查中断状态
        long sum = 0;
        System.out.println(Thread.currentThread().getName() + " 开始计算");

        while (!Thread.currentThread().isInterrupted()) {
            // 执行非阻塞计算
            for (int i = 0; i < 1_000_000; i++) {
                sum += i;
            }

            System.out.println("计算结果: " + sum);
            sum = 0;
        }

        System.out.println(Thread.currentThread().getName() + " 检测到中断信号,优雅退出");
    });

    worker.start();

    try {
        Thread.sleep(100); // 让worker线程有时间开始工作
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("主线程发送中断请求");
    worker.interrupt();
}

自定义阻塞方法的中断处理

当使用显式锁(如 ReentrantLock)或自定义阻塞机制时,需要特别注意中断处理:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;

public static void customBlockingInterruptDemo() {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    Thread waitingThread = new Thread(() -> {
        try {
            // 可中断的锁获取
            lock.lockInterruptibly();
            try {
                System.out.println("线程获取到锁,等待条件");
                // 等待条件,可被中断
                condition.await();
                System.out.println("条件满足,继续执行");
            } finally {
                lock.unlock();
            }
        } catch (InterruptedException e) {
            System.out.println("线程在等待锁或条件时被中断: " + e.getMessage());
        }
    });
    waitingThread.setName("custom-waiting-thread");

    waitingThread.start();

    try {
        Thread.sleep(1000); // 确保等待线程已经进入等待状态
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("主线程发送中断请求");
    waitingThread.interrupt();
}

中断机制的核心概念

  • interrupt()方法是一种协作式而非强制式的线程中断机制
  • 线程可以选择如何响应中断请求,甚至可以完全忽略它
  • 良好的设计应确保线程能够及时检查并响应中断请求
flowchart TD
    A[线程正常执行] -->|"调用interrupt()"| B{线程状态?}
    B -->|阻塞状态\nsleep/wait/join| C[抛出InterruptedException\n中断标志被清除]
    B -->|运行状态| D[设置中断标志位]
    C -->|捕获异常| E{是否需要退出?}
    D -->|"周期性检查\nisInterrupted()"| F{标志为true?}
    E -->|是| G[重设中断标志\n线程退出]
    E -->|否| H[继续执行]
    F -->|是| I[执行清理\n线程退出]
    F -->|否| J[继续执行]

    style C fill:#f9a,stroke:#333
    style D fill:#9cf,stroke:#333
    style G fill:#cfc,stroke:#333
    style I fill:#cfc,stroke:#333

isInterrupted() vs Thread.interrupted():

public static void interruptFlags() {
    Thread thread = new Thread(() -> {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("线程工作中...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // sleep()方法被中断会清除中断标志
                // 需要重新设置中断标志位以便外层循环检测
                Thread.currentThread().interrupt();
                System.out.println("中断发生,退出循环");
                break;
            }
        }
    });

    thread.start();

    // 主线程休眠3秒后中断工作线程
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    thread.interrupt();
}

正确处理 InterruptedException 的模式

public void run() {
    try {
        while (!Thread.currentThread().isInterrupted()) {
            // 执行任务

            // 执行可能被中断的阻塞操作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // 重新设置中断标志并退出循环
                Thread.currentThread().interrupt();
                break;
            }
        }
    } finally {
        // 执行清理工作
        System.out.println("线程结束,执行清理");
    }
}

四、守护线程(Daemon Thread)

守护线程是一种特殊的线程,它在后台为其他非守护线程提供服务。当所有非守护线程结束时,无论守护线程是否完成工作,JVM 都会退出。

守护线程的特点

  1. 当 JVM 中只剩下守护线程时,JVM 会退出
  2. 必须在线程启动前设置守护状态
  3. 典型应用:垃圾回收器、监控线程、心跳线程等

守护线程示例

public static void daemonDemo() {
    Thread daemonThread = new Thread(() -> {
        while (true) {
            try {
                System.out.println("守护线程工作中...");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                break;
            }
        }
    });
    daemonThread.setName("daemon-thread");

    // 设置为守护线程(必须在start之前)
    daemonThread.setDaemon(true);
    daemonThread.start();

    // 主线程工作3秒
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println("主线程结束,程序将退出");
    // 程序退出,守护线程也会终止
}

为何守护线程的 finally 块不保证执行?

当 JVM 准备退出时,如果只剩下守护线程,JVM 会强制终止这些线程而不等待它们完成当前工作。这意味着:

public static void daemonFinalizationDemo() {
    Thread daemon = new Thread(() -> {
        try {
            System.out.println("守护线程开始执行");
            Thread.sleep(5000); // 模拟长时间操作
            System.out.println("守护线程完成工作"); // 可能不会执行
        } catch (InterruptedException e) {
            System.out.println("守护线程被中断");
        } finally {
            System.out.println("守护线程的finally块"); // 可能不会执行
            // 关键资源清理如文件关闭、连接释放可能不会发生
        }
    });

    daemon.setDaemon(true);
    daemon.start();

    // 主线程很快结束
    System.out.println("主线程结束,JVM准备退出");
}

守护线程应用场景

  1. 日志收集线程:周期性地将日志刷新到磁盘
  2. 缓存清理线程:后台清理过期缓存
  3. 心跳检测线程:定期发送心跳包
  4. 服务监控线程:监控服务健康状态

守护线程注意事项

  1. 守护线程创建的线程也是守护线程
  2. 守护线程不应该用于执行 I/O 操作,因为它们可能在操作完成前被强制终止
  3. finally 块在守护线程中不保证一定会执行
  4. 不要将连接池或事务性操作放在守护线程中

实践建议:不要在守护线程中执行以下操作:

  • 文件或数据库操作(可能导致数据损坏)
  • 网络连接管理(可能导致连接泄漏)
  • 事务处理(可能导致事务不完整)
graph TD
    A[JVM启动] --> B[创建用户线程]
    A --> C[创建守护线程]
    B --> D[用户线程运行]
    C --> E[守护线程运行]
    D --> F[用户线程结束]
    E --> G[守护线程继续]
    F -->|所有用户线程结束| H[JVM准备退出]
    G --> G
    H --> I[强制终止所有守护线程]
    I --> J[JVM退出]

    style B fill:#9cf,stroke:#333
    style C fill:#cfc,stroke:#333
    style D fill:#9cf,stroke:#333
    style E fill:#cfc,stroke:#333
    style F fill:#9cf,stroke:#333
    style G fill:#cfc,stroke:#333
    style I fill:#f9a,stroke:#333

五、线程常见问题及解决方案

1. 线程执行不成功

问题描述:创建了线程对象,但没有执行任务

常见原因

  • 调用 run()而不是 start()
  • 线程对象被垃圾回收
  • 主线程结束太快

解决方案

// 正确启动线程
Thread thread = new Thread(() -> {
    System.out.println("任务执行");
});
thread.start(); // 不是thread.run()

// 保持对线程的引用
// 如果需要,可以使用join等待线程完成

2. 线程无法停止

问题描述:尝试停止一个正在运行的线程

常见错误:使用已废弃的 stop()、suspend()方法

为什么不应使用 stop()方法?

Thread.stop()方法在 Java 1.2 版本就被标记为废弃,主要原因包括:

  1. 不安全的资源释放:stop()会立即终止线程,不给线程任何机会执行清理工作,可能导致:
  • 文件句柄未关闭
  • 数据库连接未释放
  • 网络套接字未关闭
  • 临时文件未删除
  1. 数据不一致:强制终止可能使对象处于不一致状态
// 假设有转账操作
synchronized void transfer(Account from, Account to, int amount) {
    from.debit(amount); // 假如在这行之后被stop()
    to.credit(amount);  // 这行永远不会执行
    // 结果:钱从from账户扣除了,但没有加到to账户
}
  1. 锁状态不确定:线程被 stop()时会释放所有锁,但可能导致受保护数据的不一致状态暴露给其他线程

推荐解决方案:使用中断机制或状态标志位

// 通过标志位控制线程结束
class StoppableTask implements Runnable {
    private volatile boolean stopRequested = false;

    public void requestStop() {
        stopRequested = true;
    }

    @Override
    public void run() {
        while (!stopRequested) {
            // 执行任务
            System.out.println("线程工作中...");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                // 响应中断
                Thread.currentThread().interrupt(); // 重设中断标志
                break;
            }
        }
        System.out.println("线程正常退出");
    }
}

// 使用示例
public static void stoppableThreadDemo() {
    StoppableTask task = new StoppableTask();
    Thread thread = new Thread(task);
    thread.start();

    // 主线程工作3秒
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // 请求线程停止
    task.requestStop();
}

3. 死锁问题

问题描述:两个或多个线程互相等待对方持有的锁,形成环路等待

死锁示例

public static void deadlockDemo() {
    Object resource1 = new Object();
    Object resource2 = new Object();

    Thread thread1 = new Thread(() -> {
        synchronized (resource1) {
            System.out.println("线程1获取资源1");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("线程1等待资源2");
            synchronized (resource2) {
                System.out.println("线程1获取资源2");
            }
        }
    });
    thread1.setName("deadlock-thread-1");

    Thread thread2 = new Thread(() -> {
        synchronized (resource2) {
            System.out.println("线程2获取资源2");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("线程2等待资源1");
            synchronized (resource1) {
                System.out.println("线程2获取资源1");
            }
        }
    });
    thread2.setName("deadlock-thread-2");

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

其他常见并发问题

除了死锁外,多线程编程中还存在以下并发问题:

线程饥饿(Starvation)

当线程长时间无法获取所需资源而无法执行时,就会发生线程饥饿:

public static void starvationDemo() {
    Object lock = new Object();

    // 创建一个高优先级线程,它会长时间占用锁
    Thread highPriorityThread = new Thread(() -> {
        synchronized (lock) {
            System.out.println("高优先级线程获取到锁");
            while (true) {
                // 持续占用锁不释放
            }
        }
    });
    highPriorityThread.setPriority(Thread.MAX_PRIORITY);
    highPriorityThread.setName("high-priority");

    // 创建低优先级线程,它很难获取到锁
    Thread lowPriorityThread = new Thread(() -> {
        synchronized (lock) {
            //
            System.out.println("低优先级线程获取到锁");
        }
    });
    lowPriorityThread.setPriority(Thread.MIN_PRIORITY);
    lowPriorityThread.setName("low-priority");

    highPriorityThread.start();
    lowPriorityThread.start();
}

避免饥饿的策略

  • 使用公平锁(如 ReentrantLock(true))
  • 避免长时间持有锁
  • 适当调整线程优先级

注意:在现代 JVM 中,线程优先级的效果可能不如预期。优先级调整是一种建议而非强制执行的机制,不同操作系统实现差异很大。推荐优先通过合理的锁使用策略(减少锁持有时间、使用公平锁等)来避免线程饥饿,而不是过度依赖优先级调整。

活锁(Livelock)

当线程不断相互礼让,都无法向前推进时,就会发生活锁:

public static void simpleLivelockDemo() {
    final AtomicBoolean firstThreadGiveWay = new AtomicBoolean(true);
    final AtomicBoolean secondThreadGiveWay = new AtomicBoolean(true);

    Thread thread1 = new Thread(() -> {
        while (true) {
            // 第一个线程检查"对方是否在让路"
            if (secondThreadGiveWay.get()) {
                // 如果对方在让路,自己也让路
                System.out.println("线程1说:对方在让路,我也让路");
                firstThreadGiveWay.set(true);
            } else {
                // 如果对方不让路,自己也不让了,开始工作
                System.out.println("线程1说:对方不让路了,我开始工作");
                break;
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                return;
            }
        }
    });

    Thread thread2 = new Thread(() -> {
        while (true) {
            // 第二个线程检查"对方是否在让路"
            if (firstThreadGiveWay.get()) {
                // 如果对方在让路,自己也让路
                System.out.println("线程2说:对方在让路,我也让路");
                secondThreadGiveWay.set(true);
            } else {
                // 如果对方不让路,自己也不让了,开始工作
                System.out.println("线程2说:对方不让路了,我开始工作");
                break;
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                return;
            }
        }
    });

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

    // 这个活锁将永远持续下去,因为两个线程都在互相礼让
    // 在实际应用中,可以通过随机等待或优先级解决
}

避免活锁的策略

  • 引入随机因素(如随机等待时间)
  • 使用优先级或资源排序
  • 设置超时机制
graph TD
    A[正常执行] --> B{并发问题类型}
    B -->|线程无法前进| C{问题具体类型}
    C -->|互相持有对方需要的锁| D[死锁Deadlock]
    C -->|互相礼让导致无法前进| E[活锁Livelock]
    C -->|低优先级线程长期得不到执行| F[饥饿Starvation]
    D --> G[环路等待线程完全阻塞]
    E --> H[线程持续运行但无法完成任务]
    F --> I[部分线程几乎不被调度]

    style D fill:#f9a,stroke:#333
    style E fill:#9cf,stroke:#333
    style F fill:#fcf,stroke:#333

4. 线程异常处理

线程中未捕获的异常不会被传递到主线程,需要特殊处理:

public static void exceptionHandlerDemo() {
    Thread thread = new Thread(() -> {
        System.out.println("线程开始执行");
        throw new RuntimeException("线程执行异常");
    });
    thread.setName("exception-thread");

    // 设置未捕获异常处理器
    thread.setUncaughtExceptionHandler((t, e) -> {
        System.out.println("捕获到线程 " + t.getName() + " 的异常:" + e.getMessage());
        // 可以记录日志、发送告警等
    });

    thread.start();
}

六、总结

类别方法/状态特点注意事项
线程状态NEW线程已创建未启动使用 start()方法启动
RUNNABLE包含就绪和运行中线程获得 CPU 时间片才真正运行
BLOCKED等待获取 synchronized 锁避免长时间阻塞
WAITING无限期等待其他线程操作可能导致程序挂起
TIMED_WAITING有超时时间的等待设置合理的超时时间
TERMINATED线程执行完毕可以用 isAlive()检查
线程控制start()启动新线程不能重复调用
run()普通方法调用,不创建新线程不要直接调用 run()方法
sleep()线程休眠,不释放锁处理 InterruptedException
yield()线程让步,提示调度器效果不可靠,依赖系统实现
join()等待其他线程完成可能导致当前线程阻塞
interrupt()中断线程,协作式机制结合 isInterrupted()使用
线程类型用户线程默认线程类型JVM 等待用户线程结束
守护线程为其他线程服务的后台线程不执行重要任务,finally 块不保证执行
并发问题死锁线程互相等待对方持有的锁按固定顺序获取锁,使用 tryLock()
活锁线程不断相互礼让引入随机因素,设置优先级
饥饿线程长期无法获取资源使用公平锁,避免长时间持有锁

通过本文,我们深入了解了 Java 线程的生命周期和基础操作方法。掌握这些知识对于编写高质量的多线程程序至关重要。在实际开发中,请记住:理解线程状态转换、合理使用线程控制方法、妥善处理线程异常、选择适当的线程通信机制,这些都是构建稳定多线程应用的基石。

在下一篇文章中,我们将探讨线程安全问题与基本解决方案,敬请期待!


感谢您耐心阅读到这里!如果觉得本文对您有帮助,欢迎点赞 👍、收藏 ⭐、分享给需要的朋友,您的支持是我持续输出技术干货的最大动力!

如果想获取更多 Java 技术深度解析,欢迎点击头像关注我,后续会每日更新高质量技术文章,陪您一起进阶成长~


异常君
1 声望1 粉丝

在 Java 的世界里,永远有下一座技术高峰等着你。我愿做你登山路上的同频伙伴,陪你从看懂代码到写出让自己骄傲的代码。咱们,代码里见!