Java基础快速入门: 线程状态与线程池详解

本文纲要

  1. 线程的生命周期与状态
    概念介绍:线程的六种状态
    状态转换图 (Mermaid)

  2. 线程池基本原理
    为什么需要线程池
    线程池的思想模型

  3. 使用 Executors 创建线程池
    newCachedThreadPool 创建弹性线程池
    newFixedThreadPool 创建固定大小线程池
    代码演示与参数说明

  4. 深入 ThreadPoolExecutor 自定义线程池
    七个核心参数的故事映射
    参数详解 (时间单位、队列、工厂)
    任务拒绝策略的触发时机

  5. 四种拒绝策略实战
    AbortPolicy
    DiscardPolicy
    DiscardOldestPolicy
    CallerRunsPolicy

  6. 项目代码结构

线程的生命周期与状态

在多线程编程中,每一个线程从创建到消亡,都会经历一系列状态。Java 虚拟机 (JVM) 中定义了线程的 六种状态,注意,虚拟机并未定义“运行状态”,因为运行状态是线程与操作系统 CPU 直接交互时的状态。

  • 新建 (NEW):创建线程对象,尚末调用 start()
  • 就绪 (RUNNABLE):调用 start() 后,线程获得执行资格,等待 CPU 调度。
  • 运行 (JVM 未单独定义):线程真正获得 CPU 时间片,执行代码。
  • 阻塞 (BLOCKED):线程试图获取一个锁(如 synchronized),但锁已被其他线程持有。
  • 等待 (WAITING):线程执行了 wait() 方法,进入无限等待,直到被 notify/notifyAll 唤醒。
  • 计时等待 (TIMED_WAITING):线程执行了 sleep(millis) 或带超时的 wait(millis),在指定时间内等待,时间结束后回到就绪态。
  • 结束 (TERMINATED):线程的 run() 方法执行完毕,线程对象销毁。

这些状态在 Thread 类的内部枚举 State 中定义:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED

下面是线程状态转换图:

创建线程对象

start()

获得CPU执行权

CPU时间片被抢走

无法获取锁

获取到锁

wait()

notify()/notifyAll()

sleep(millis)

时间到/notify

run()执行完毕

NEW

RUNNABLE

运行

BLOCKED

WAITING

TIMED_WAITING

TERMINATED

注意:虚拟机中没有直接定义“运行”状态,而是将“就绪”和“运行”统一表示为 RUNNABLE

线程池基本原理

1 ) 为什么需要线程池?

如果每次执行任务都手动 new Thread 然后启动,会有两个问题:

  • 频繁创建和销毁线程,开销大(浪费时间);
  • 线程对象无法复用,浪费系统资源(浪费内存)。

线程池采用“池化思想”,预先创建一定数量的线程放入池中,有任务时从池中取线程执行,执行完毕归还而不是销毁,从而复用线程,减少开销。

2 ) 工作原理比喻

可以把线程池比作一个“碗柜”,线程对象就像是“碗”:

  • 初始时碗柜是空的。
  • 第一次需要线程(吃饭)时,去创建一个(买碗),用完放回碗柜。
  • 以后再需要线程时,直接从碗柜取一个空闲的,不再重复创建。

对应线程池流程:

提交任务

池中有空闲线程?

取出空闲线程执行任务

当前线程数 < 最大线程数?

创建新线程执行任务

任务放入阻塞队列等待

任务执行完毕

线程归还到池中

等待空闲线程

使用 Executors 创建线程池

Java 提供了 Executors 工具类,可以快速创建常用的线程池。

1 ) 创建默认线程池 newCachedThreadPool

创建一个可根据需要创建新线程的线程池,空闲线程存活 60 秒后回收,适合执行大量短期异步任务。

代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class MyThreadPoolDemo {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个默认的线程池对象. 池子中默认是空的.
        ExecutorService executorService = Executors.newCachedThreadPool();
        // Executors --- 帮助我们创建线程池 
        // ExecutorService --- 帮助我们控制线程池 
 
        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " 在执行了");
        });
 
        // Thread.sleep(2000); // 如果主线程休眠,可以看到线程复用效果 
 
        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " 在执行了");
        });
 
        executorService.shutdown(); // 关闭线程池 
    }
}

说明:

  • submit 方法提交任务(RunnableCallable),线程池自动分配线程执行。
    如果第一个任务执行很快,线程被归还后,第二个任务会复用该线程(线程名相同)。
    若不调用 shutdown(),程序不会退出,因为池中线程还在存活。

2 ) 创建固定大小线程池 newFixedThreadPool

指定池中最大线程数,一旦创建,线程数量固定,空闲线程不会被回收。

代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
 
public class MyThreadPoolDemo2 {
    public static void main(String[] args) {
        // 参数不是初始值而是最大值 
        ExecutorService executorService = Executors.newFixedThreadPool(10);
 
        ThreadPoolExecutor pool = (ThreadPoolExecutor) executorService;
        System.out.println(pool.getPoolSize()); // 0 
 
        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " 在执行了");
        });
 
        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + " 在执行了");
        });
 
        System.out.println(pool.getPoolSize()); // 2 
        // executorService.shutdown();
    }
}

关键点:

  • newFixedThreadPool(10) 创建时,池内线程数为 0(不是初始10个线程)。
  • 通过 getPoolSize() 查看当前线程数量。
  • 提交两个任务后,线程数变为 2,不会超过设定的最大值。

深入 ThreadPoolExecutor 自定义线程池

Executors 的静态方法本质上都是通过 ThreadPoolExecutor 实现的。
我们可以直接使用它创建更灵活的线程池。

1 ) 七个核心参数的故事对照

我们用“饭店雇佣服务员”的比喻来理解这七个参数:

参数数量参数名称 (饭店比喻)线程池参数说明
1正式员工数量核心线程数 (corePoolSize)一直在岗,即使空闲也不被辞退
2最大员工数 (正式+临时)最大线程数 (maximumPoolSize)必须 ≥ 核心线程数
3临时工空闲后多久辞退 (值)空闲线程存活时间 (keepAliveTime)数值,如 60
4时间单位 (秒/分/时)时间单位 (TimeUnit)例如 TimeUnit.SECONDS
5等待区排队顾客数任务队列 (BlockingQueue)当线程数达到核心数且全忙,新任务在队列中等待
6从哪里招人线程工厂 (ThreadFactory)默认使用 Executors.defaultThreadFactory()
7客满排队拒接策略拒绝策略 (RejectedExecutionHandler)超过最大线程+队列容量时的处理方式

2 ) 构造自定义线程池

项目结构(代码目录):

mythreadpool/
├── MyRunnable.java 
├── MyThreadPoolDemo3.java 
├── MyThreadPoolDemo4.java 
├── MyThreadPoolDemo5.java 
├── MyThreadPoolDemo6.java 
└── MyThreadPoolDemo7.java 

MyRunnable 任务类:

package com.wb.mythreadpool;
 
public class MyRunnable implements Runnable {
    @Override 
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 在执行了");
    }
}

使用所有七个参数的线程池:

package com.wb.mythreadpool;
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
public class MyThreadPoolDemo3 {
    // 参数一:核心线程数量 
    // 参数二:最大线程数 
    // 参数三:空闲线程最大存活时间 
    // 参数四:时间单位 
    // 参数五:任务队列 
    // 参数六:创建线程工厂 
    // 参数七:任务的拒绝策略 
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,                              // 核心线程数 
                5,                              // 最大线程数 
                2,                              // 空闲线程存活时间 
                TimeUnit.SECONDS,               // 时间单位秒 
                new ArrayBlockingQueue<>(10),   // 任务队列容量10 
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy() // 默认拒绝策略 
        );
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.shutdown();
    }
}

3 ) 参数详解

  • 时间单位 TimeUnit:枚举类型,常用 TimeUnit.SECONDS(秒)、TimeUnit.HOURS(小时)等,不能直接传字符串。
  • 任务队列:当核心线程都在忙,但尚未达到最大线程数时,新任务会先放入队列。队列满且线程数达到最大时,新任务触发拒绝策略。常用 ArrayBlockingQueue
  • 线程工厂:默认工厂 Executors.defaultThreadFactory() 会创建普通非守护线程,并设置线程名和优先级。
  • 拒绝策略触发公式
    • 提交任务数 > 最大线程数 + 队列容量 时,触发拒绝。
  • 例如:最大线程 5,队列容量 10,提交 16 个任务(5+10=15,16>15)时就会拒绝。

演示代码 (MyThreadPoolDemo4):

package com.wb.mythreadpool;
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
public class MyThreadPoolDemo4 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,
                5,
                2,
                TimeUnit.HOURS,
                new ArrayBlockingQueue<>(10),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
 
        // 提交 16 个任务(最大5+队列10=15,16>15),将触发拒绝 
        for (int i = 1; i <= 16; i++) {
            pool.submit(new MyRunnable());
        }
        pool.shutdown();
    }
}

运行将抛出 RejectedExecutionException 异常,表明多余的任务被拒绝。

四种拒绝策略实战

Java 线程池定义了四种 RejectedExecutionHandler 实现类,我们可以通过更换第 7 个参数来选择不同策略。

拒绝策略类策略描述是否推荐
AbortPolicy丢弃任务并抛出 RejectedExecutionException是 (默认)
DiscardPolicy丢弃任务但不抛异常不推荐
DiscardOldestPolicy丢弃队列中最旧的任务,然后重新尝试提交当前任务按需使用
CallerRunsPolicy由调用线程(提交任务的线程)直接执行该任务可调优

1 ) AbortPolicy(默认)

代码见上文 MyThreadPoolDemo4,提交数超过阈值时抛出异常,导致程序中断。

2 ) DiscardPolicy – 静默丢弃

package com.wb.mythreadpool;
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
public class MyThreadPoolDemo5 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1,
                2,
                2,
                TimeUnit.HOURS,
                new ArrayBlockingQueue<>(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardPolicy());
 
        for (int i = 1; i <= 5; i++) {
            int y = i;
            pool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "-----" + y);
            });
        }
        pool.shutdown();
    }
}

结果分析:最大线程 2 + 队列 1 = 3,5 个任务中只有 3 个会被执行,其余 2 个被偷偷丢弃,不会报错。控制台只输出三个任务的信息。

3 ) DiscardOldestPolicy – 丢弃最旧任务

package com.wb.mythreadpool;
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
public class MyThreadPoolDemo6 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1,
                2,
                2,
                TimeUnit.HOURS,
                new ArrayBlockingQueue<>(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());
 
        for (int i = 1; i <= 10; i++) {
            int y = i;
            pool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "-----" + y);
            });
        }
        pool.shutdown();
    }
}

效果:同样最多执行 3 个任务,但它会丢弃队列中等待最久的那个旧任务,将新任务加入队列,因此最后执行的任务很可能是较晚提交的任务(如最后一个任务 10 被保留)。

4 ) CallerRunsPolicy – 调用者执行

package com.wb.mythreadpool;
 
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
public class MyThreadPoolDemo7 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                1,
                2,
                2,
                TimeUnit.HOURS,
                new ArrayBlockingQueue<>(1),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
 
        for (int i = 1; i <= 10; i++) {
            int y = i;
            pool.submit(() -> {
                System.out.println(Thread.currentThread().getName() + "-----" + y);
            });
        }
        pool.shutdown();
    }
}

现象:某些任务由 main 线程执行(其他任务由池内线程执行)。因为当线程池和队列都满时,提交任务的线程(此处为主线程)会自己执行该任务,减缓提交速度,起到一定的“流控”作用。

总结

项目代码结构

mythread/
└── src/
    └── com/
        └── wb/
            └── mythreadpool/
                ├── MyRunnable.java 
                ├── MyThreadPoolDemo.java    (newCachedThreadPool)
                ├── MyThreadPoolDemo2.java   (newFixedThreadPool)
                ├── MyThreadPoolDemo3.java   (自定义线程池基础)
                ├── MyThreadPoolDemo4.java   (AbortPolicy 触发)
                ├── MyThreadPoolDemo5.java   (DiscardPolicy)
                ├── MyThreadPoolDemo6.java   (DiscardOldestPolicy)
                └── MyThreadPoolDemo7.java   (CallerRunsPolicy)

所有示例代码均已给出,可直接运行观察不同线程池和拒绝策略的行为。掌握线程状态和线程池是 Java 高并发编程的基础,建议结合代码动手实践,加深理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wang's Blog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值