MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java 线程池状态的整理阶段

2024-11-233.7k 阅读

Java 线程池状态概述

在深入探讨 Java 线程池状态的整理阶段之前,我们先来了解一下线程池状态的整体概念。Java 线程池的状态是用来标识线程池当前所处的运行情况,它对于线程池的控制和管理至关重要。线程池的状态主要通过一个原子整数变量 ctl 来表示,这个变量不仅包含了线程池的状态信息,还记录了线程池中当前活动线程的数量。

java.util.concurrent.ThreadPoolExecutor 类中,ctl 是这样定义的:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

这里 ctlOf 方法用于将线程池状态和线程数合并成 ctl 的值,而 RUNNING 是线程池的初始状态。

线程池的基本状态

  1. RUNNING
    • 含义:这是线程池的初始状态。在线程池处于 RUNNING 状态时,它可以接受新的任务并且处理阻塞队列中的任务。
    • 状态值RUNNING 的状态值是一个负数,具体来说,它的值为 (1 << COUNT_BITS) * -1,其中 COUNT_BITS 是用来表示线程数的位数,在 32 位系统中,COUNT_BITS 通常为 29,这意味着 RUNNING 的状态值约为 -1 << 29
    • 代码示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolRunningStateExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        // 此时线程池处于RUNNING状态
        System.out.println("线程池状态: " + ((ThreadPoolExecutor) executorService).getPoolState());
        executorService.submit(() -> {
            System.out.println("执行任务");
        });
        executorService.shutdown();
    }
}

在这个示例中,通过 Executors.newFixedThreadPool(5) 创建的线程池初始状态就是 RUNNING,我们可以通过 ((ThreadPoolExecutor) executorService).getPoolState() 方法获取其状态(实际 ThreadPoolExecutor 并没有直接提供 getPoolState 方法,这里为了示例示意,假设存在这样的方法,实际可通过 ctl 相关逻辑判断)。

  1. SHUTDOWN
    • 含义:当调用线程池的 shutdown() 方法后,线程池会进入 SHUTDOWN 状态。在这个状态下,线程池不再接受新的任务,但会继续处理阻塞队列中已有的任务。
    • 状态值SHUTDOWN 的状态值为 0
    • 代码示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadPoolShutdownStateExample {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.submit(() -> {
            System.out.println("执行任务1");
        });
        executorService.submit(() -> {
            System.out.println("执行任务2");
        });
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
                if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                    System.err.println("Pool did not terminate");
                }
            }
        } catch (InterruptedException ie) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
        // 此时线程池处于SHUTDOWN状态,不再接受新任务,但会处理队列中的任务
    }
}

在这个示例中,调用 shutdown() 方法后,线程池进入 SHUTDOWN 状态,此时提交新任务会被拒绝,但已在队列中的任务会继续执行。

  1. STOP
    • 含义:当调用线程池的 shutdownNow() 方法后,线程池会进入 STOP 状态。在这个状态下,线程池不仅不再接受新任务,而且会尝试停止当前正在执行的任务,并清空阻塞队列。
    • 状态值STOP 的状态值为 (1 << COUNT_BITS)
    • 代码示例
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolStopStateExample {
    public static void main(String[] args) {
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
        ThreadPoolExecutor executorService = new ThreadPoolExecutor(
                2, 5, 10, TimeUnit.SECONDS, taskQueue);
        executorService.submit(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
                System.out.println("任务执行中");
            } catch (InterruptedException e) {
                System.out.println("任务被中断");
            }
        });
        executorService.shutdownNow();
        // 此时线程池处于STOP状态,尝试停止正在执行的任务并清空队列
    }
}

在这个示例中,调用 shutdownNow() 方法后,线程池进入 STOP 状态,正在执行的任务可能会被中断,队列中的任务会被清空。

  1. TIDYING
    • 含义:当所有任务都已终止(包括正在执行的任务被停止以及队列中的任务被处理完),并且线程池中的活动线程数为 0 时,线程池会进入 TIDYING 状态。在这个状态下,会执行 terminated() 方法,这个方法可以被重写来进行一些自定义的收尾工作。
    • 状态值TIDYING 的状态值为 (1 << COUNT_BITS) + 1
    • 代码示例
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTidyingStateExample {
    public static void main(String[] args) {
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
        ThreadPoolExecutor executorService = new ThreadPoolExecutor(
                2, 5, 10, TimeUnit.SECONDS, taskQueue) {
            @Override
            protected void terminated() {
                System.out.println("线程池进入TIDYING状态,执行自定义收尾工作");
            }
        };
        executorService.submit(() -> {
            System.out.println("执行任务");
        });
        executorService.shutdown();
        try {
            if (executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                // 所有任务执行完毕,线程池进入TIDYING状态
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,当所有任务执行完毕且活动线程数为 0 时,线程池进入 TIDYING 状态,并执行重写的 terminated() 方法。

  1. TERMINATED
    • 含义:当 terminated() 方法执行完毕后,线程池会进入 TERMINATED 状态。这表示线程池已经彻底终止,不再有任何活动。
    • 状态值TERMINATED 的状态值为 (1 << COUNT_BITS) + 2
    • 代码示例
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolTerminatedStateExample {
    public static void main(String[] args) {
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
        ThreadPoolExecutor executorService = new ThreadPoolExecutor(
                2, 5, 10, TimeUnit.SECONDS, taskQueue) {
            @Override
            protected void terminated() {
                System.out.println("线程池进入TIDYING状态,执行自定义收尾工作");
            }
        };
        executorService.submit(() -> {
            System.out.println("执行任务");
        });
        executorService.shutdown();
        try {
            if (executorService.awaitTermination(60, TimeUnit.SECONDS)) {
                // 所有任务执行完毕,线程池进入TIDYING状态
                // 当terminated()方法执行完毕后,进入TERMINATED状态
                System.out.println("线程池已进入TERMINATED状态");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,当 terminated() 方法执行完毕,线程池进入 TERMINATED 状态。

线程池状态转换的内部机制

  1. 从 RUNNING 到 SHUTDOWN
    • 触发条件:当调用线程池的 shutdown() 方法时,线程池从 RUNNING 状态转换为 SHUTDOWN 状态。
    • 内部实现:在 ThreadPoolExecutorshutdown() 方法中,首先会通过 compareAndSetState(RUNNING, SHUTDOWN) 方法尝试将线程池状态从 RUNNING 转换为 SHUTDOWN。如果转换成功,会遍历工作线程并中断空闲线程(通过 interruptIdleWorkers() 方法),这样可以使得空闲线程尽快结束等待,去处理队列中的任务。
public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // 用于子类扩展
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

这里 advanceRunState(SHUTDOWN) 方法内部使用 compareAndSetState 来更新状态,interruptIdleWorkers() 方法如下:

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}
  1. 从 RUNNING 或 SHUTDOWN 到 STOP
    • 触发条件:当调用线程池的 shutdownNow() 方法时,无论线程池当前是 RUNNING 还是 SHUTDOWN 状态,都会转换为 STOP 状态。
    • 内部实现:在 shutdownNow() 方法中,首先通过 compareAndSetState 尝试将状态转换为 STOP。然后,会遍历工作线程并中断所有线程(通过 interruptWorkers() 方法),同时清空任务队列。
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

这里 interruptWorkers() 方法会中断所有工作线程:

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

drainQueue() 方法用于清空任务队列:

private List<Runnable> drainQueue() {
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<>();
    q.drainTo(taskList);
    if (!q.isEmpty()) {
        for (Runnable r : q.toArray(new Runnable[0])) {
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}
  1. 从 SHUTDOWN 或 STOP 到 TIDYING
    • 触发条件:当所有任务都已终止(正在执行的任务被停止以及队列中的任务被处理完),并且线程池中的活动线程数为 0 时,线程池从 SHUTDOWNSTOP 状态转换为 TIDYING 状态。
    • 内部实现:在 tryTerminate() 方法中,会进行状态转换的判断。如果当前状态是 SHUTDOWN 且任务队列已空,或者当前状态是 STOP 且活动线程数为 0,就会尝试将状态转换为 TIDYING,并调用 terminated() 方法。
final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        if (isRunning(c) ||
           runStateAtLeast(c, TIDYING) ||
           (runStateOf(c) == SHUTDOWN &&!workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}
  1. 从 TIDYING 到 TERMINATED
    • 触发条件:当 terminated() 方法执行完毕后,线程池从 TIDYING 状态转换为 TERMINATED 状态。
    • 内部实现:在 tryTerminate() 方法中,当成功将状态转换为 TIDYING 并调用 terminated() 方法后,会通过 ctl.set(ctlOf(TERMINATED, 0)) 将状态设置为 TERMINATED,并通过 termination.signalAll() 唤醒所有等待在 termination 条件变量上的线程。

理解线程池状态在实际应用中的重要性

  1. 资源管理
    • 了解线程池的状态有助于进行有效的资源管理。例如,当线程池处于 SHUTDOWN 状态时,我们知道它不再接受新任务,但仍在处理队列中的任务,这时候可以合理地等待其处理完毕,避免资源的浪费。如果在不合适的时候强制停止线程池(如在任务关键阶段进入 STOP 状态),可能会导致数据不一致或任务不完整。
    • 代码示例:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolResourceManagementExample {
    public static void main(String[] args) {
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
        ThreadPoolExecutor executorService = new ThreadPoolExecutor(
                2, 5, 10, TimeUnit.SECONDS, taskQueue);
        executorService.submit(() -> {
            // 模拟一个长时间运行的任务
            try {
                TimeUnit.SECONDS.sleep(5);
                System.out.println("长时间运行任务执行完毕");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 先进入SHUTDOWN状态,等待任务自然结束
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
                // 如果10秒内未完成,再考虑进入STOP状态
                executorService.shutdownNow();
                if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
                    System.err.println("Pool did not terminate");
                }
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}

在这个示例中,先进入 SHUTDOWN 状态等待任务完成,如果超时未完成再进入 STOP 状态,这样可以更好地管理资源,避免过早终止任务。

  1. 错误处理和监控
    • 线程池状态可以作为监控和错误处理的重要依据。例如,如果线程池意外进入 STOP 状态,可能意味着有异常情况发生,需要进行排查。通过监控线程池状态的变化,可以及时发现并处理潜在的问题,保证系统的稳定性。
    • 代码示例:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolErrorHandlingExample {
    private static ThreadPoolExecutor executorService;

    static {
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
        executorService = new ThreadPoolExecutor(
                2, 5, 10, TimeUnit.SECONDS, taskQueue);
    }

    public static void monitorThreadPool() {
        new Thread(() -> {
            while (true) {
                int state = executorService.getPoolState(); // 假设存在获取状态的方法
                if (state == ThreadPoolExecutor.STOP) {
                    System.err.println("线程池意外进入STOP状态,进行排查");
                    // 这里可以添加具体的排查逻辑,如记录日志、通知运维等
                }
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    public static void main(String[] args) {
        monitorThreadPool();
        executorService.submit(() -> {
            // 模拟任务执行
            System.out.println("执行任务");
        });
        // 模拟一些可能导致异常进入STOP状态的操作(这里省略实际异常触发逻辑)
    }
}

在这个示例中,通过一个线程定时监控线程池状态,如果发现进入 STOP 状态,就进行相应的错误处理。

  1. 性能优化
    • 理解线程池状态有助于进行性能优化。例如,在系统负载较低时,可以通过合理调整线程池状态(如从 RUNNING 进入 SHUTDOWN 再到 TERMINATED)来释放资源,降低系统开销。而在负载增加时,确保线程池处于 RUNNING 状态并能高效处理任务。
    • 代码示例:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadPoolPerformanceOptimizationExample {
    private static ThreadPoolExecutor executorService;

    static {
        BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
        executorService = new ThreadPoolExecutor(
                2, 5, 10, TimeUnit.SECONDS, taskQueue);
    }

    public static void adjustThreadPoolAccordingToLoad() {
        // 模拟根据负载调整线程池状态
        boolean isLowLoad = true; // 假设通过某种方式判断当前负载低
        if (isLowLoad) {
            executorService.shutdown();
            try {
                if (executorService.awaitTermination(10, TimeUnit.SECONDS)) {
                    System.out.println("线程池已优雅关闭,释放资源");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else {
            // 如果负载高,确保线程池处于RUNNING状态并调整参数等
        }
    }

    public static void main(String[] args) {
        adjustThreadPoolAccordingToLoad();
    }
}

在这个示例中,根据负载情况调整线程池状态,实现性能优化。

总结线程池状态相关要点

  1. 状态转换的条件和顺序
    • 线程池状态从 RUNNING 开始,可能通过 shutdown() 方法转换为 SHUTDOWN,或通过 shutdownNow() 方法转换为 STOP。从 SHUTDOWNSTOP 状态,在满足任务终止且活动线程数为 0 的条件下转换为 TIDYING,最后在 terminated() 方法执行完毕后转换为 TERMINATED
    • 理解这个顺序对于正确控制线程池至关重要,错误的状态转换操作可能导致任务丢失、资源未释放等问题。
  2. 对任务执行的影响
    • 不同的线程池状态对任务执行有不同的影响。RUNNING 状态下正常接受和处理任务,SHUTDOWN 状态不再接受新任务但处理队列任务,STOP 状态停止正在执行任务并清空队列。TIDYINGTERMINATED 状态表示任务执行的收尾和终结阶段。
    • 在编写多线程应用程序时,必须考虑线程池状态对任务执行的影响,以确保任务的完整性和正确性。
  3. 应用场景和最佳实践
    • 在实际应用中,根据不同的业务需求选择合适的线程池状态转换时机。例如,在系统关闭时,优先选择 shutdown() 方法进行优雅关闭,给任务足够的时间完成。在紧急情况下,如系统出现严重错误,可能需要使用 shutdownNow() 方法强制停止线程池。
    • 同时,结合监控和错误处理机制,及时发现和处理线程池状态异常,保证系统的稳定性和可靠性。

通过深入理解 Java 线程池状态及其转换机制,开发者能够更好地控制和管理线程池,提高多线程应用程序的性能和稳定性。在实际开发中,根据具体业务场景合理运用线程池状态相关知识,是实现高效并发编程的关键之一。