1 线程的核心概念

1.1 进程与线程的定义

进程是操作系统进行资源分配的最小单位,一个正在运行的应用程序对应一个独立进程,拥有独立的内存地址空间。
线程是操作系统进行任务调度的最小单位,是进程内部的一条独立执行流。一个进程至少包含一个主线程,同一进程内的多个线程共享进程的内存资源与文件资源。

1.2 进程与线程的核心差异

对比维度进程线程
资源定位系统分配资源的最小单位系统调度 CPU 执行的最小单位
内存空间进程间内存相互隔离,独立寻址同一进程内线程共享堆内存与方法区,各线程私有栈空间与程序计数器
故障影响单个进程崩溃不影响其他进程单个线程异常可能导致整个进程终止
调度开销创建、销毁、上下文切换开销较高创建、销毁、切换开销远低于进程

1.3 Java线程与操作系统线程的关系

线程是操作系统内核提供的原生能力,Java 标准库中的 java.lang.Thread 类是对操作系统线程 API 的抽象与封装。调用线程对象的start()方法,才会通过操作系统内核创建并启动一条原生线程;重写run()方法仅用于定义线程的执行逻辑,直接调用run()不会创建新线程,代码仍在当前调用线程中串行执行。

2 线程的创建方式

2.1 继承Thread类

通过继承 Thread 类并重写 run() 方法定义线程任务,创建子类实例后调用 start() 启动线程。该方式受 Java 单继承机制限制,适用于线程类无需继承其他父类的场景。

class CustomThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 执行中");
    }
}

public class ThreadCreateDemo1 {
    public static void main(String[] args) {
        CustomThread thread = new CustomThread();
        thread.start();
    }
}

2.2 实现Runnable接口

通过实现 Runnable 接口定义任务逻辑,再将任务实例传入 Thread 构造方法。该方式将任务定义与线程控制解耦,支持类继承其他父类,是更推荐的实现方式。

class CustomTask implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 执行中");
    }
}

public class ThreadCreateDemo2 {
    public static void main(String[] args) {
        Thread thread = new Thread(new CustomTask());
        thread.start();
    }
}

2.3 匿名内部类实现

对于一次性使用的线程任务,可通过匿名内部类快速创建,无需单独定义实现类。

public class ThreadCreateDemo3 {
    public static void main(String[] args) {
        // 继承Thread的匿名内部类
        Thread t1 = new Thread() {
            @Override
            public void run() {
                System.out.println("匿名Thread实现");
            }
        };
        t1.start();

        // 实现Runnable的匿名内部类
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名Runnable实现");
            }
        });
        t2.start();
    }
}

2.4 Lambda 表达式实现

Runnable 为函数式接口,可通过 Lambda 表达式简化代码,是实际开发中最常用的简洁写法。


public class ThreadCreateDemo4 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> System.out.println("Lambda方式创建线程"));
        thread.start();
    }
}

3 Thread类核心API

3.1 线程基础属性

方法功能描述
long getId()返回线程的唯一标识 ID
String getName()返回线程名称,主要用于日志调试
Thread.State getState()返回线程当前的生命周期状态
int getPriority()返回线程优先级,优先级仅作为调度参考,不保证执行顺序
boolean isDaemon()判断是否为守护线程;JVM 会在所有非守护线程终止后退出
boolean isAlive()判断线程是否处于存活状态,NEW 与 TERMINATED 状态返回 false
boolean isInterrupted()判断线程是否被设置中断标记,调用后不清除标记

3.2 线程休眠:sleep()

Thread.sleep(long millis) 为静态方法,使当前线程进入限时等待状态,暂停执行指定时长。该方法不释放持有的锁,且实际休眠时长大于等于参数设定值,具体恢复时间由操作系统调度决定。

3.3 线程等待:join()

join()方法使当前调用线程阻塞,直到目标线程执行完毕后再继续执行,用于协调多线程的执行顺序。

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread task = new Thread(() -> {
            // 任务逻辑
        });
        task.start();
        task.join(); // 主线程等待task执行完成
    }
}

3.4 线程中断机制

Java 采用协作式中断机制,通过设置中断标记通知线程终止,而非强制抢占式终止。

  1. 自定义标记位:通过 volatile 修饰的布尔变量作为退出标志,线程循环判断该标记决定是否终止。
  2. interrupt() 方法:调用线程对象的 interrupt() 方法设置中断标记。若线程处于 sleep/wait/join 阻塞状态,会抛出 InterruptedException 并清除标记;若线程处于运行状态,仅设置标记,需通过 Thread.currentThread().isInterrupted() 主动检测。

4 线程状态与流转

线程的生命周期由Thread.State枚举定义,共包含六种状态。

4.1 六种状态定义

状态定义
NEW线程对象已创建,尚未调用 start() 方法
RUNNABLE可运行状态,包含正在 CPU 执行与就绪等待调度两种子状态
BLOCKED阻塞状态,线程等待获取 synchronized 锁
WAITING无限等待状态,需由其他线程主动唤醒
TIMED_WAITING限时等待状态,达到指定时长后自动唤醒
TERMINATED终止状态,线程执行逻辑已完成

4.2 状态流转逻辑

  • 调用 start() 后,线程从 NEW 进入 RUNNABLE 状态。
  • 线程尝试获取锁失败时,进入 BLOCKED 状态;获取锁后回到 RUNNABLE。
  • 调用 wait()、无超时 join() 时,线程释放锁并进入 WAITING 状态;被唤醒后重新竞争锁,成功后回到 RUNNABLE。
  • 调用 sleep()、带超时 wait()/join() 时,线程进入 TIMED_WAITING 状态;超时或被唤醒后回到 RUNNABLE。
  • 线程执行完毕后进入 TERMINATED 状态。

5 线程安全问题分析

5.1 线程安全的定义

在多线程并发执行环境下,程序运行结果与单线程串行执行的预期结果一致,则称该程序是线程安全的。

5.2 线程不安全的核心成因

(1)原子性缺失

原子性指一段操作要么完整执行,要么完全不执行,执行过程不会被其他线程打断。
Java 中看似原子的代码(如count++)实际包含“读内存-计算-写回内存”三步操作。多线程抢占式调度下,操作中途可能被切换,导致共享数据修改异常。

(2)内存可见性问题

Java 内存模型(JMM)规定:所有共享变量存储于主内存,每个线程拥有独立的工作内存(对应 CPU 寄存器与高速缓存)。线程操作共享变量时,先从主内存拷贝副本到工作内存,修改后再同步回主内存。
由于线程工作内存的存在,一个线程对共享变量的修改可能无法立即被其他线程感知,导致数据不一致。

(3)指令重排序

编译器与 CPU 为优化性能,会在不改变单线程语义的前提下调整指令执行顺序。在多线程场景下,指令重排序可能导致跨线程逻辑不一致。

6 线程安全的基础解决方案

6.1 synchronized 关键字

synchronized 即监视器锁,是 Java 内置的互斥同步机制,能够保证原子性、内存可见性,并禁止部分指令重排序。

核心特性
  1. 互斥性:同一时刻仅一个线程能获取指定对象的锁,进入同步代码块;其他尝试获取同一锁的线程会阻塞等待。锁状态存储于 Java 对象头中。
  2. 可重入性:同一线程可重复获取同一把锁,不会出现自身死锁。锁内部维护持有者线程与计数器,每加锁一次计数器递增,解锁时递减,计数器归零时锁真正释放。
三种使用形式
public class SyncDemo {
    private final Object locker = new Object();

    // 1. 修饰代码块,指定锁对象
    public void method1() {
        synchronized (locker) {
            // 同步逻辑
        }
    }

    // 2. 修饰普通方法,锁对象为当前实例this
    public synchronized void method2() {
        // 同步逻辑
    }

    // 3. 修饰静态方法,锁对象为当前类的Class对象
    public static synchronized void method3() {
        // 同步逻辑
    }
}
标准库中的线程安全类
  • 线程不安全类:ArrayList、HashMap、HashSet、StringBuilder
  • 线程安全类:StringBuffer、Vector、Hashtable;高并发场景推荐使用ConcurrentHashMap

6.2 volatile 关键字

volatile是轻量级同步机制,仅作用于变量,能够保证内存可见性与禁止指令重排序,但不保证原子性。

作用机制
  • 写 volatile 变量时,强制将工作内存中的修改立即刷新回主内存。
  • 读 volatile 变量时,强制从主内存读取最新值,绕过线程工作内存缓存。
适用场景与局限性

适用于一写多读的场景,如状态标记位。不适用于 count++ 等需要原子性的复合操作,此类场景仍需通过锁机制保证线程安全。

7 线程间协作:wait/notify 机制

wait()、notify()、notifyAll() 是 Object 类的原生方法,用于实现线程间的等待-唤醒协作,必须在同步代码块中以锁对象调用。

7.1 核心方法语义

方法语义
wait()当前线程释放锁,进入对象的等待队列,无限期等待唤醒
wait(long timeout)限时等待,超时后自动唤醒
notify()随机唤醒等待队列中的一个线程;调用后不会立即释放锁,退出同步块后锁才释放
notifyAll()唤醒等待队列中所有线程,唤醒后所有线程重新竞争锁

需注意:wait() 应置于循环中调用,避免虚假唤醒导致逻辑异常。

7.2 wait 与 sleep 的差异

对比维度wait()sleep()
所属类Object类方法Thread类静态方法
锁要求必须在同步块中调用无需持有锁
锁行为调用后释放持有的锁调用后不释放锁
用途线程间协作通信单纯使线程暂停执行

8 多线程经典应用场景

8.1 单例模式

单例模式保证一个类在程序运行期间仅存在一个实例。

饿汉式实现

类加载时即创建实例,实现简单且天然线程安全;缺点是类加载即初始化,可能造成资源浪费。


class SingletonHungry {
    private static final SingletonHungry INSTANCE = new SingletonHungry();
    private SingletonHungry() {}
    public static SingletonHungry getInstance() {
        return INSTANCE;
    }
}
双重校验锁懒汉式实现

首次调用时才创建实例,通过双重 if 判断降低锁竞争,结合 volatile 禁止指令重排序,兼顾线程安全与性能。

class SingletonLazy {
    private static volatile SingletonLazy instance = null;
    private SingletonLazy() {}

    public static SingletonLazy getInstance() {
        if (instance == null) {
            synchronized (SingletonLazy.class) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

8.2 阻塞队列与生产者消费者模型

阻塞队列是线程安全的队列结构,遵循先进先出原则:队列满时,入队操作阻塞;队列空时,出队操作阻塞。

阻塞队列是生产者消费者模型的核心实现组件,通过队列解耦生产者与消费者,平衡两者处理能力,实现削峰填谷。
Java 标准库提供 BlockingQueue 接口,常用实现为 LinkedBlockingQueue,核心方法为 put()(阻塞入队)与 take()(阻塞出队)。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class ProducerConsumer {
    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();

        Thread producer = new Thread(() -> {
            while (true) {
                int num = (int) (Math.random() * 1000);
                try {
                    queue.put(num);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者");

        Thread consumer = new Thread(() -> {
            while (true) {
                try {
                    queue.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者");

        producer.start();
        consumer.start();
    }
}

8.3 线程池

核心价值

频繁创建与销毁线程会产生较高开销。线程池通过复用一组已创建的线程执行任务,降低线程生命周期开销,提升系统响应速度。

Executors工具类

Executors提供线程池快速创建方法:

  • newFixedThreadPool(int n):创建固定线程数的线程池
  • newCachedThreadPool():创建线程数动态增长的线程池
  • newSingleThreadExecutor():创建单线程线程池
  • newScheduledThreadPool(int n):创建支持定时任务的线程池
ThreadPoolExecutor核心参数

生产环境推荐手动创建ThreadPoolExecutor,自定义核心参数:

  1. corePoolSize:核心线程数,常驻线程数量
  2. maximumPoolSize:最大线程数,核心线程与临时线程总数上限
  3. keepAliveTime:临时线程空闲存活时长
  4. workQueue:存放待执行任务的阻塞队列
  5. threadFactory:线程创建工厂
  6. RejectedExecutionHandler:任务拒绝策略,包含抛出异常、调用者执行、丢弃最旧任务、丢弃新任务四种实现。

9 线程安全设计思路

  1. 无共享设计:线程使用私有局部变量,从根源避免数据竞争。
  2. 只读设计:共享变量仅进行读操作,天然线程安全。
  3. 同步控制:
    • 复合操作通过synchronized或显式锁保证原子性;
    • 状态标记通过volatile保证内存可见性;
    • 优先使用JDK内置并发工具类,避免手动实现复杂同步逻辑。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐