Skip to content

线程介绍与使用(合并)

Phase 5(per docs/plans/2026-07-01-1500-content-consolidation-and-home-fixes.md):把原 1-线程介绍.md(线程基础概念)与 2-线程使用.md(线程创建与使用)合并到本文件。前者已迁至 note/archive/2026-07-01-合并/

第一部分:线程基础

一、线程和进程

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。

线程个数最优公式 N(thread) = (1 + T(io)/T(cpu)) * Ncpu

进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位,进程维护资源,线程是真正的执行体。

包含关系:一个进程可以创建多个线程,且最少一个主线程,线程只能属于一个进程,线程是进程的一部分。

二、上下文切换

CPU 为线程分配时间片,时间片非常短(毫秒级别),CPU 不停地切换线程执行,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。上下文的频繁切换会带来一定的性能开销。

如何减少上下文切换的开销?

  • 无锁并发编程:多线程竞争锁时,会引起上下文切换;可以用一些办法来避免使用锁,如将数据的 ID 按照 Hash 算法取模分段,不同的线程处理不同段的数据。
  • CAS:Java 的 Atomic 包使用 CAS 算法来更新数据,而不需要加锁。
  • 使用最少线程:避免创建不需要的线程,比如任务很少却创建了很多线程,造成大量线程都处于等待状态。
  • 协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换(主要是 Go 语言)。

三、线程的状态

操作系统的状态:

状态含义
NEW该状态下的线程还没开始执行
READY线程等待获取 CPU 的时间片
RUNNING线程正在运行
WAITING线程等待
TERMINATING该状态下的线程已经退出

Java 的状态:

状态含义
NEW该状态下的线程还没开始执行
RUNNABLE该线程正运行中
BLOCKED该线程被阻塞,主要等待 synchronized 语句块的锁
WAITING无限期等待另外一条线程执行特定的操作(执行了 object.wait()join()LockSupport.part()
TIMED_WAITING在特定时间内等待另外一条线程执行某种操作(执行了 Object.wait(long)Thread.join()LockSupport.parkNanos()LockSupport.parkUtil),会在特定时间内自行返回
TERMINATED该状态下的线程已经退出

四、忙循环

忙循环用一个循环让一个线程等待,不像 waitsleep 会放弃 CPU 的控制权,而是一直在执行当前的程序,这是为了保留 CPU 的缓存。在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避免重建缓存和减少等待重建的时间可以使用忙循环。

五、线程挂起

线程的挂起是为了使线程进入"非可执行"状态,这个状态下的线程 CPU 不会分给线程时间片,进入这个状态用来暂停一个线程的运行。

为什么要挂起:CPU 分配的时间片非常短,同时也非常珍贵,为了避免资源的浪费。

挂起线程的操作:

  1. 废弃的方法thread.suspend() 该方法不会释放资源,可能会导致死锁,所以废弃;thread.resume() 跟着 suspend() 存在,也跟着废弃。
  2. 可以使用的方法wait() 方法可以挂起线程,会释放资源(包括锁和 CPU 资源),notify() 可以唤醒一个该锁的线程,notifyAll() 是唤醒所有锁的线程。这几个方法必须持有锁对象,必须在同步的方法体内。

使用场景:当所有资源没有准备到位的时候,需要挂起等到所有资源都拿到了再执行。

六、线程中断

stop() 方法已经被废弃,因为该方法会强行关闭线程,会导致线程安全问题。

使用 interrupt() 方法可以使线程中断,但是要视情况而定:不使用线程池时直接使用该方法;使用线程池时需要根据标记位去判断线程的状态。

java
/**
 * @Description 中断线程
 */
public class InterruptThread {

    /**
     * 要保证原子性操作,如果不使用原子操作的类,需要加 volatile
     */
    private static AtomicBoolean interrupt = new AtomicBoolean(true);

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = new ThreadPoolExecutor(0, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>());

        //手动中断线程的方法
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("手动的线程正在运行");
            }
            System.out.println("手动的线程结束");
        });

        thread.start();

        //线程池中断
        executorService.execute(() -> {
            while (interrupt.get()) {
                System.out.println("线程池的线程正在运行");
            }
            System.out.println("线程池的线程结束");
        });
        Thread.sleep(1L);

        //线程中断
        thread.interrupt();
        //让线程池里的这个线程结束
        interrupt.set(false);
    }
}

七、线程优先级

线程优先级的高低跟数字的大小相反,优先级为 1 最高,10 最低。

线程的优先级告诉程序线程的重要程度,当线程堵塞的时候,程序会尽可能先运行优先级比较高的线程,优先级比较低的线程被准许运行的概率会比较低。

八、守护线程

线程分为用户线程和守护线程。守护线程是指整个程序中所有用户线程的守护者,只要有活着的用户线程,守护线程就活着。当 JVM 中最后一个非守护线程结束时,守护线程也跟着 JVM 一起退出。

尽量少使用守护线程,因为不可控制。不要在守护线程当中进行读写操作、执行计算逻辑等等。


第二部分:线程使用

九、Runnable 方法的定义

Runnable 接口为关联 Thread 对象的线程提供执行代码。这些代码放在 Runnablerun 方法当中,这个方法没有返回值但是可能会抛出异常。

十、手动创建线程的方式

1. 继承 Thread 类创建线程

java
public class MyThread extends Thread {
    public void run() {
        // 重写 run 方法
    }
}

2. 实现 Runnable 接口创建线程

java
public class MyThread2 implements Runnable {
    public void run() {
        // 重写 run 方法
    }
}

3. 使用 Callable 和 Future 创建线程

java
CallableTest callableTest = new CallableTest();
// 因为 Callable 接口是函数式接口,可以使用 Lambda 表达式
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
    int i = 0;
    for (; i < 100; i++) {
        System.out.println(Thread.currentThread().getName() + "的循环变量i的值:" + i);
    }
    return i;
});
for (int i = 0; i < 100; i++) {
    System.out.println(Thread.currentThread().getName() + "的循环变量i:" + i);
    if (i == 20) {
        new Thread(task, "有返回值的线程").start();
    }
}
try {
    System.out.println("子线程返回值:" + task.get());
} catch (Exception e) {
    e.printStackTrace();
}

4. 使用线程池(Executor 框架)

java
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
//    ExecutorService executorService = Executors.newFixedThreadPool(5);
//    ExecutorService executorService = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 5; i++) {
        executorService.execute(new TestRunnable());
        System.out.println("************* a" + i + " *************");
    }
    executorService.shutdown();
}
class TestRunnable implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程被调用了。");
    }
}

十一、Spring Boot 创建线程池

线程池配置文件:

java
/**
 * @Description 线程池配置文件
 */
@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Bean
    public ThreadPoolTaskExecutor defaultThreadPool() {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        // 核心线程数量
        threadPoolTaskExecutor.setCorePoolSize(2);
        // 最大线程数量
        threadPoolTaskExecutor.setMaxPoolSize(5);
        // 队列中最大任务数
        threadPoolTaskExecutor.setQueueCapacity(2);
        // 线程名称前缀
        threadPoolTaskExecutor.setThreadNamePrefix("ThreadPool-");
        // 当达到最大线程数时如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
        threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 线程空闲后最大存活时间
        threadPoolTaskExecutor.setKeepAliveSeconds(60);
        // 初始化线程池
        threadPoolTaskExecutor.initialize();
        return threadPoolTaskExecutor;
    }
}

线程池使用:

java
@Autowired
private ThreadPoolTaskExecutor threadPool;

public void test() {
    threadPool.execute(() -> {
        System.out.println("已经发送过短信");
    });
}