前言:上一篇提到线程池的核心参数,本篇详细讲一下线程的概念、创建以及线程安全的问题。

        线程是 Java 并发编程的基础,简单来说,线程是程序执行的最小单元,一个进程可以包含多个线程,多个线程共享进程的内存空间(如堆、方法区),但每个线程有自己独立的栈和程序计数器。下面我会从基础到核心,由浅入深讲解线程的关键知识点。

一、线程的基础概念

1. 进程 vs 线程

  • 进程:操作系统分配资源的基本单位(如内存、CPU 时间片),每个进程有独立的内存空间,比如你打开的微信、IDEA 都是一个进程。
  • 线程:CPU 调度的基本单位,依赖进程存在,多个线程共享进程资源,切换成本远低于进程。比如微信同时接收消息、刷新朋友圈,就是多个线程在工作。

2. 线程的生命周期(五大状态)

Java 线程的生命周期包含 5 个核心状态,状态之间的切换是开发中重要的知识点:

  • NEW(新建):创建了线程对象但未调用 start() 方法,此时线程还未启动。
  • RUNNABLE(可运行):调用 start() 后,线程进入该状态(包含「就绪」和「运行中」两个细分状态),等待 CPU 调度。
  • BLOCKED(阻塞):线程因竞争同步锁(如 synchronized)失败而阻塞。
  • WAITING(无限等待):线程调用 wait()join()(无参)等方法,需其他线程主动唤醒。
  • TIMED_WAITING(计时等待):线程调用 sleep(ms)wait(ms)join(ms) 等方法,等待超时自动唤醒或被提前唤醒。
  • TERMINATED(终止):线程执行完 run() 方法或因异常终止。

二、线程的创建方式

Java 中创建线程有 3 种标准方式,核心是「定义线程要执行的任务」:

1. 继承 Thread 类

重写 run() 方法(线程执行的核心逻辑),通过 start() 启动线程(注意:直接调用 run() 只是普通方法调用,不会创建新线程)。

public class ThreadDemo1 extends Thread {
    // 重写run方法,定义线程执行逻辑
    @Override
    public void run() {
        System.out.println("线程执行:" + Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        // 创建线程对象
        ThreadDemo1 thread = new ThreadDemo1();
        // 启动线程(底层调用start0() native方法,由JVM创建操作系统线程)
        thread.start(); 
        // 输出:线程执行:Thread-0
    }
}

2. 实现 Runnable 接口

解耦「线程」和「任务」,更推荐使用(因为 Java 单继承,实现接口更灵活):

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

    public static void main(String[] args) {
        // 1. 创建任务对象
        RunnableDemo task = new RunnableDemo();
        // 2. 将任务交给Thread执行
        Thread thread = new Thread(task, "自定义线程名");
        // 3. 启动线程
        thread.start();
        // 输出:线程执行:自定义线程名
    }
}

3. 实现 Callable 接口(带返回值 + 可抛异常)

适合需要线程执行后返回结果的场景,结合 FutureTask 使用:

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableDemo implements Callable<Integer> {
    // 重写call方法(带返回值,可抛异常)
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) throws Exception {
        // 1. 创建Callable任务
        CallableDemo task = new CallableDemo();
        // 2. 包装成FutureTask(实现了RunnableFuture,兼具Runnable和Future特性)
        FutureTask<Integer> futureTask = new FutureTask<>(task);
        // 3. 启动线程
        new Thread(futureTask).start();
        // 4. 获取线程执行结果(get()会阻塞,直到线程执行完成)
        Integer result = futureTask.get();
        System.out.println("1-100求和结果:" + result); // 输出:5050
    }
}

三、线程的核心操作方法

方法 作用 注意事项
start() 启动线程,JVM 底层创建操作系统线程 一个线程只能调用一次,多次调用会抛 IllegalThreadStateException
run() 线程执行的核心逻辑 直接调用是普通方法,不会创建新线程
sleep(long ms) 让线程进入计时等待状态,不释放锁 静态方法,作用于当前线程;需捕获 InterruptedException
wait()/notify()/notifyAll() 线程间通信,需在 synchronized 代码块中调用 wait() 释放锁,sleep() 不释放锁;由 Object 类提供(所有对象都有锁)
join() 等待该线程执行完成后,当前线程再继续 比如 t1.join(),主线程会等 t1 执行完再执行
yield() 线程主动让出 CPU 时间片,回到就绪状态 只是「建议」,CPU 不一定会采纳
interrupt() 中断线程(设置中断标记),不会强制终止 需结合 isInterrupted() 检查标记,优雅终止线程
setDaemon(true) 设置为守护线程

守护线程依赖用户线程,所有用户线程结束后,守护线程自动终止(如 GC 线程)

经典示例:线程中断(优雅终止)

public class ThreadInterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            // 循环检查中断标记
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程运行中...");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // sleep时被中断,会清除中断标记,需手动重置
                    Thread.currentThread().interrupt();
                    System.out.println("线程被中断,准备退出");
                }
            }
        });
        t1.start();
        // 主线程等待3秒后中断t1
        Thread.sleep(3000);
        t1.interrupt();
    }
}

四、线程安全问题

1. 什么是线程安全?

多个线程同时操作共享资源时,程序执行结果与单线程执行结果一致,且符合预期,就是线程安全。反之则是线程不安全(如多线程卖票导致超卖、余额计算错误)。

2. 线程不安全的根本原因

  • 共享资源(如静态变量、成员变量)
  • 多线程同时写操作
  • 指令重排序 / CPU 缓存(可见性问题)
  • 原子性缺失(如 i++ 实际是 读-改-写 三步,非原子操作)

3. 解决线程安全的核心方案

(1)synchronized 关键字(内置锁)
  • 作用:保证原子性、可见性、有序性。
  • 用法:修饰方法、修饰代码块。
// 修饰方法(锁对象是当前实例)
public synchronized void add() {
    count++;
}

// 修饰代码块(指定锁对象,更灵活)
public void add() {
    synchronized (this) {
        count++;
    }
}
(2)Lock 接口(显式锁,如 ReentrantLock)

synchronized 更灵活(可尝试获取锁、可中断、可超时):

import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {
    private final ReentrantLock lock = new ReentrantLock();
    private int count = 0;

    public void add() {
        lock.lock(); // 获取锁
        try {
            count++; // 核心操作
        } finally {
            lock.unlock(); // 释放锁(必须放finally,避免异常导致锁泄漏)
        }
    }
}
(3)原子类(java.util.concurrent.atomic)

基于 CAS(Compare and Swap)实现无锁并发,性能更高,适合简单变量操作:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo {
    private AtomicInteger count = new AtomicInteger(0);

    public void add() {
        count.incrementAndGet(); // 原子自增,等价于count++,但线程安全
    }
}

总结

  1. 线程基础:线程是 CPU 调度的最小单位,生命周期包含 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 6 种状态(常说 5 种是合并了 RUNNABLE 的细分状态);创建线程有继承 Thread、实现 Runnable、实现 Callable 三种方式,推荐后两种。
  2. 线程安全:核心是解决共享资源的并发问题,常用方案有 synchronized(内置锁)、Lock(显式锁)、原子类(CAS),其中原子类性能最优(无锁)。
  3. 上一篇线程池:实际开发优先使用线程池管理线程,避免频繁创建 / 销毁线程;推荐自定义线程池(Executors 工具类的默认实现可能有内存溢出风险),核心参数包括核心线程数、最大线程数、工作队列、拒绝策略。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐