Java基础十:线程启动和关闭

319 阅读5分钟

进程(Process)与线程(Thread)

进程(Process)
  • 定义

    • 进程是系统进行资源分配和调度的独立单元,包含程序计数器、环境变量集、内存空间等系统资源,是程序的一次动态执行过程。
  • 特点

    • 进程间相互独立,每个进程拥有各自的内存空间和系统资源。
    • 进程间通信需通过特定机制,如管道、消息队列、共享内存等。
  • Java与进程

    • Java程序启动时自动创建进程,但Java不直接操作进程,而是由操作系统管理。
    • Java提供Runtime.getRuntime().exec(String command)等API来与本地进程交互,执行本地命令或程序。
线程(Thread)
  • 定义

    • 线程是CPU调度和分派的基本单位,比进程更小。线程共享进程中的内存空间和系统资源,但拥有独立的执行栈和程序计数器。
  • 特点

    • 线程间共享进程的内存空间,但每个线程的执行栈和程序计数器是独立的。
    • 线程间通信和同步变得尤为重要,需要确保数据一致性和避免竞争条件。
  • Java中的线程

    • Java直接支持线程,通过java.lang.Thread类和实现Runnable接口来创建线程。
    • Java线程有五种状态:新建(NEW)、就绪(RUNNABLE)、运行(RUNNING,实际与RUNNABLE合并)、阻塞(BLOCKED)、终止(TERMINATED)。
线程状态详解
  • 新建(NEW)

    • 线程已创建但尚未启动。
  • 就绪(RUNNABLE)

    • 线程可被执行,但尚未分配到CPU时间片。
  • 运行(RUNNING)

    • 实际执行中,但Java中将RUNNABLE和RUNNING视为同一状态。
  • 阻塞(BLOCKED)

    • 线程因某些原因(如等待I/O、锁等)暂停执行。
  • 等待(WAITING)

    • 无限期等待其他线程操作,如Object.wait()Thread.join()
  • 限时等待(TIMED_WAITING)

    • 等待其他线程操作,但有时间限制,如Thread.sleep(long millis)
  • 终止(TERMINATED)

    • 线程执行完毕。
线程的启动与停止
  • 启动

    • 通过调用线程的start()方法,使线程进入就绪状态,等待CPU分配时间片执行。
  • 停止

    • Java不推荐直接停止线程,因为可能导致资源泄露或数据不一致。应使用以下方法:
      • 标志位:通过共享布尔变量控制线程执行循环。
      • interrupt()方法:请求线程中断自己,并在适当时候响应中断(如通过抛出InterruptedException)。
      • FutureExecutorService:通过取消Future任务来停止线程池中的线程。
线程退出策略
  • 正常退出

    • run()方法执行完毕。
  • interrupt()退出

    • 线程检查中断状态,并响应中断(如结束循环)。
  • 守护线程(Daemon Thread)

    • 随JVM退出而自动退出,但通常不推荐用于重要任务。
示例代码
  • 使用标志位

    public class MyThread extends Thread {
        private volatile boolean running = true;
    
        public void run() {
            while (running) {
                try {
                    // 执行任务
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    running = false;
                }
            }
        }
    
        public void stopThread() {
            running = false;
        }
    }
    
  • 使用interrupt()

    • 当线程A调用线程B的interrupt()方法时,线程B的中断状态会被设置为true。如果线程B正在等待、休眠或阻塞,则会抛出InterruptedException,线程B可以捕获这个异常并处理。
  • 线程已经sleep半小时了,如何退出

    • 在另一个线程中调用该线程的interrupt()方法,并在sleep调用所在的线程中捕获InterruptedException异常,然后处理(如退出循环)。

如果线程已经调用Thread.sleep(30 * 60 * 1000)(即半小时)并且你想中断它,你可以:

  1. 在另一个线程中调用该线程的interrupt()方法。
  2. sleep调用所在的线程中,捕获InterruptedException异常,并适当处理(如退出循环)。
try {
    Thread.sleep(30 * 60 * 1000); // 尝试休眠半小时
} catch (InterruptedException e) {
    // 处理中断,比如退出线程
    Thread.currentThread().interrupt(); // 可选,如果中断状态对后续操作重要
    return; // 或其他退出逻辑
}
public class MyThread extends Thread {
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                // 执行任务
                Thread.sleep(100);
            }
        } catch (InterruptedException e) {
            // 处理中断
        }
    }
}
  • 使用FutureExecutorService

  • 原理:如果你使用的是ExecutorService来管理线程池,则可以通过Future对象来取消任务的执行。Future.cancel(mayInterruptIfRunning)方法可以用来尝试取消任务的执行。

  • 实现

    • 提交任务到ExecutorService,并获取返回的Future对象。
    • 使用Future.cancel(true)来尝试中断正在执行的任务(如果mayInterruptIfRunningtrue)。
    • 关闭ExecutorService以释放资源。
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Future<?> future = executor.submit(() -> {
        // 执行长时间运行的任务
    });
    
    future.cancel(true); // 尝试中断任务
    executor.shutdown(); // 关闭线程池
    
  • 使用FutureExecutorService

    • 通过ExecutorService管理线程池,使用Future.cancel(true)尝试中断正在执行的任务。
sleepwait方法的异同
  • 相同点

    • 两者都会让当前线程暂停执行。
  • 不同点

    • 调用方式:sleep是Thread类的方法,可在任何地方调用;wait是Object类的方法,只能在同步方法或同步块中调用。
    • 锁释放:sleep不释放锁;wait释放锁,并在被唤醒时重新获取锁。
    • 等待时间:sleep的暂停时间是固定的;wait可以等待直到被其他线程通知或超时。
    • 异常处理:两者都抛出InterruptedException,但wait后通常需要再次检查条件。
如何唤醒
  • wait:可通过其他线程调用该对象的notify()notifyAll()方法来唤醒。
  • sleep:无法被其他线程直接唤醒,它会在指定的时间后自动醒来。
notifynotifyAll的区别
  • notify():唤醒在此对象监视器上等待的单个线程(选择是任意的)。
  • notifyAll():唤醒在此对象监视器上等待的所有线程,这些线程在获取对象的锁之后竞争继续执行。