JAVA 多线程编程:从基础原理到实战应用

1. 引言

在当今多核处理器普及的时代,多线程编程已经成为 Java 开发者必须掌握的核心技能之一。无论是提升程序性能、优化用户体验,还是处理高并发业务场景,多线程都扮演着至关重要的角色。本文将带你从多线程的基础原理出发,逐步深入到实战应用,帮助你系统性地掌握 Java 多线程编程。

2. 线程基础概念

2.1 什么是线程

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

2.2 进程与线程的区别

  • 进程:操作系统资源分配的基本单位,拥有独立的地址空间。
  • 线程:CPU 调度的基本单位,共享进程的地址空间和资源。

简单来说,进程是"资源容器",线程是"执行单元"。多个线程共享同一个进程的内存空间,因此线程间的通信比进程间通信更高效,但也带来了数据同步的挑战。

2.3 线程的生命周期

Java 线程在其生命周期中会经历以下几种状态:

  • 新建(NEW):线程对象创建后,尚未启动。
  • 就绪(RUNNABLE):线程已启动,等待 CPU 调度。
  • 阻塞(BLOCKED):线程等待获取锁。
  • 等待(WAITING):线程无限期等待其他线程。
  • 超时等待(TIMED_WAITING):线程在指定时间内等待。
  • 终止(TERMINATED):线程执行完毕。

下面是线程六种状态之间的转换关系图:
#mermaid-svg-2D6NBrD2PqyjNKp6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2D6NBrD2PqyjNKp6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2D6NBrD2PqyjNKp6 .error-icon{fill:#552222;}#mermaid-svg-2D6NBrD2PqyjNKp6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2D6NBrD2PqyjNKp6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2D6NBrD2PqyjNKp6 .marker.cross{stroke:#333333;}#mermaid-svg-2D6NBrD2PqyjNKp6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2D6NBrD2PqyjNKp6 p{margin:0;}#mermaid-svg-2D6NBrD2PqyjNKp6 defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-2D6NBrD2PqyjNKp6 g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-2D6NBrD2PqyjNKp6 g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-2D6NBrD2PqyjNKp6 g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-2D6NBrD2PqyjNKp6 g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-2D6NBrD2PqyjNKp6 g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-2D6NBrD2PqyjNKp6 .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-2D6NBrD2PqyjNKp6 .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-2D6NBrD2PqyjNKp6 .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2D6NBrD2PqyjNKp6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2D6NBrD2PqyjNKp6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2D6NBrD2PqyjNKp6 .edgeLabel .label text{fill:#333;}#mermaid-svg-2D6NBrD2PqyjNKp6 .label div .edgeLabel{color:#333;}#mermaid-svg-2D6NBrD2PqyjNKp6 .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-2D6NBrD2PqyjNKp6 .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-2D6NBrD2PqyjNKp6 .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-2D6NBrD2PqyjNKp6 .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-2D6NBrD2PqyjNKp6 .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-2D6NBrD2PqyjNKp6 .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2D6NBrD2PqyjNKp6 #statediagram-barbEnd{fill:#333333;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .cluster-label,#mermaid-svg-2D6NBrD2PqyjNKp6 .nodeLabel{color:#131300;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-2D6NBrD2PqyjNKp6 .note-edge{stroke-dasharray:5;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-note text{fill:black;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram-note .nodeLabel{color:black;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagram .edgeLabel{color:red;}#mermaid-svg-2D6NBrD2PqyjNKp6 #dependencyStart,#mermaid-svg-2D6NBrD2PqyjNKp6 #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-2D6NBrD2PqyjNKp6 .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2D6NBrD2PqyjNKp6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建线程对象
调用 start()
等待获取锁(synchronized)
获取到锁
调用 wait()/join()/LockSupport.park()
被 notify()/notifyAll()/join 完成/unpark()
调用 sleep()/wait(timeout)/join(timeout)/LockSupport.parkNanos()
超时或被唤醒
run() 执行完毕或抛出未捕获异常
带超时等待
取消超时等待
NEW
RUNNABLE
BLOCKED
WAITING
TIMED_WAITING
TERMINATED

各状态转换的触发条件说明:

  • NEW → RUNNABLE :调用线程对象的 start() 方法后,线程进入就绪状态,等待 CPU 调度执行。
  • RUNNABLE → BLOCKED :线程尝试进入 synchronized 代码块或方法时,若锁被其他线程持有,则进入阻塞状态等待获取锁。
  • BLOCKED → RUNNABLE:持有锁的线程释放锁后,阻塞线程竞争到锁,重新回到就绪状态等待 CPU 调度。
  • RUNNABLE → WAITING :调用 wait()、join() 或 LockSupport.park() 时,线程无限期等待其他线程的通知或完成。
  • WAITING → RUNNABLE :被 notify()/notifyAll() 唤醒、join() 的线程执行完毕,或 LockSupport.unpark() 解除挂起。
  • RUNNABLE → TIMED_WAITING :调用 sleep(ms)、wait(timeout)、join(timeout) 或 LockSupport.parkNanos() 时,线程在指定时间内等待。
  • TIMED_WAITING → RUNNABLE:等待超时或被其他线程唤醒后,线程重新回到就绪状态。
  • RUNNABLE → TERMINATED :run() 方法正常执行完毕,或抛出未捕获的异常导致线程终止。

3. 创建线程的三种方式

3.1 继承 Thread 类

java 复制代码
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程运行中:" + Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }
}

3.2 实现 Runnable 接口

java 复制代码
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程运行中:" + Thread.currentThread().getName());
    }
    
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}

3.3 使用 Callable 和 Future

java 复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "线程执行结果";
    }
    
    public static void main(String[] args) throws Exception {
        FutureTask<String> task = new FutureTask<>(new MyCallable());
        Thread thread = new Thread(task);
        thread.start();
        String result = task.get();
        System.out.println("获取结果:" + result);
    }
}

推荐使用 Runnable 或 Callable 方式,因为 Java 是单继承,实现接口可以保留类的继承能力,同时更符合面向对象的设计原则。

4. 线程同步与锁机制

4.1 为什么需要线程同步

当多个线程同时访问共享数据时,可能会产生数据不一致的问题。例如,多个线程同时对同一个变量进行自增操作,由于操作不是原子性的,最终结果可能小于预期值。

4.2 synchronized 关键字

synchronized 是 Java 内置的同步机制,可以修饰方法或代码块:

java 复制代码
public class Counter {
    private int count = 0;
    
    public synchronized void increment() {
        count++;
    }
    
    public synchronized int getCount() {
        return count;
    }
}

4.3 Lock 接口

Java 5 之后提供了更灵活的 Lock 接口:

java 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CounterWithLock {
    private int count = 0;
    private final Lock lock = new ReentrantLock();
    
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

4.4 volatile 关键字

volatile 保证变量的可见性,但不保证原子性:

java 复制代码
public class FlagExample {
    private volatile boolean flag = false;
    
    public void setFlag() {
        flag = true;
    }
    
    public boolean isFlag() {
        return flag;
    }
}

5. 线程通信

5.1 wait/notify 机制

java 复制代码
public class ProducerConsumer {
    private final List<Integer> queue = new ArrayList<>();
    private final int MAX_SIZE = 10;
    
    public synchronized void produce(int value) throws InterruptedException {
        while (queue.size() == MAX_SIZE) {
            wait();
        }
        queue.add(value);
        notifyAll();
    }
    
    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait();
        }
        int value = queue.remove(0);
        notifyAll();
        return value;
    }
}

5.2 Condition 接口

使用 Condition 可以实现更精细的线程控制:

java 复制代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.LinkedList;
import java.util.Queue;

public class ConditionProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int MAX_SIZE = 5;
    private final ReentrantLock lock = new ReentrantLock();
    // 使用两个独立的 Condition,实现精确唤醒
    private final Condition notFull = lock.newCondition();   // 队列未满条件
    private final Condition notEmpty = lock.newCondition();  // 队列非空条件

    // 生产者:向队列中添加数据
    public void produce(int value) throws InterruptedException {
        lock.lock();
        try {
            // 队列满时,生产者等待 notFull 条件
            while (queue.size() == MAX_SIZE) {
                System.out.println("队列已满,生产者等待...");
                notFull.await();   // 只挂起生产者线程
            }
            queue.offer(value);
            System.out.println("生产者生产:" + value + ",当前队列大小:" + queue.size());
            // 生产成功后,精确唤醒一个消费者线程
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    // 消费者:从队列中取出数据
    public int consume() throws InterruptedException {
        lock.lock();
        try {
            // 队列空时,消费者等待 notEmpty 条件
            while (queue.isEmpty()) {
                System.out.println("队列为空,消费者等待...");
                notEmpty.await();  // 只挂起消费者线程
            }
            int value = queue.poll();
            System.out.println("消费者消费:" + value + ",当前队列大小:" + queue.size());
            // 消费成功后,精确唤醒一个生产者线程
            notFull.signal();
            return value;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ConditionProducerConsumer pc = new ConditionProducerConsumer();

        // 启动生产者线程
        Thread producer = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    pc.produce(i);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }, "生产者线程");

        // 启动消费者线程
        Thread consumer = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                try {
                    pc.consume();
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }, "消费者线程");

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

Condition 相比 wait/notify 的优势:

  • 多条件精确唤醒 :Condition 可以为不同的等待条件创建独立的 Condition 对象(如 notFull 和 notEmpty),生产者只等待 notFull,消费者只等待 notEmpty,唤醒时也精确针对目标线程。而 wait/notify 只有一个等待队列,notifyAll() 会唤醒所有线程,存在大量无效唤醒和竞争。
  • 支持中断与超时 :Condition 的 await(long time, TimeUnit unit) 支持超时等待,awaitUninterruptibly() 支持不可中断等待,比 wait 更灵活。
  • 与 Lock 配合更强大 :Condition 必须与 ReentrantLock 配合使用,可以结合 tryLock、公平锁等高级特性,实现更精细的并发控制。

6. 线程池

6.1 为什么使用线程池

频繁创建和销毁线程会带来较大的性能开销。线程池通过复用线程,减少了线程创建和销毁的开销,同时可以控制并发线程数量,避免资源耗尽。

6.2 创建线程池

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        for (int i = 0; i < 10; i++) {
            executor.execute(() -> {
                System.out.println("任务执行:" + Thread.currentThread().getName());
            });
        }
        
        executor.shutdown();
    }
}

6.2.1 四种常用线程池对比

Executors 工具类提供了多种便捷的线程池创建方法,下面通过表格对比四种最常用的线程池:

线程池类型 适用场景 核心参数 注意事项
newFixedThreadPool(n) 任务数量固定、负载稳定的场景,如固定数量的后台任务 固定线程数 n,无最大线程数限制(等于核心线程数),使用无界 LinkedBlockingQueue 队列无界,任务积压时可能占用大量内存;线程数固定,无法弹性伸缩
newCachedThreadPool() 大量短期异步任务、任务执行时间短的场景 核心线程数为 0,最大线程数为 Integer.MAX_VALUE,空闲线程 60 秒回收,使用 SynchronousQueue 线程数可无限增长,突发高并发时可能创建过多线程导致资源耗尽
newSingleThreadExecutor() 需要保证任务顺序执行、串行处理的场景 单线程,使用无界 LinkedBlockingQueue 只有一个线程,若任务阻塞则后续任务全部排队等待;队列无界同样存在内存风险
newScheduledThreadPool(n) 需要定时执行、延迟执行或周期性任务的场景 固定核心线程数 n,使用 DelayedWorkQueue 适合调度类任务,但若任务执行时间超过调度周期,会阻塞后续调度

为什么推荐使用 ThreadPoolExecutor 手动创建线程池?

Executors 提供的便捷方法虽然使用简单,但存在以下隐患:

  • 无界队列风险:newFixedThreadPool 和 newSingleThreadExecutor 使用无界 LinkedBlockingQueue,当任务积压过多时会占用大量内存,甚至引发 OOM。
  • 线程数失控风险:newCachedThreadPool 的最大线程数为 Integer.MAX_VALUE,高并发下可能创建海量线程,导致资源耗尽。
  • 参数不可控:Executors 封装了内部参数,开发者无法自定义核心线程数、队列类型、拒绝策略等关键配置。

因此,推荐直接使用 ThreadPoolExecutor 手动创建线程池,通过显式指定核心线程数、最大线程数、队列容量和拒绝策略,可以更精细地控制线程池行为,避免上述隐患,也更符合生产环境的可靠性要求。

6.3 ThreadPoolExecutor 详解

推荐直接使用 ThreadPoolExecutor 来创建线程池,以便更精细地控制参数:

java 复制代码
import java.util.concurrent.*;

public class ThreadPoolExecutorExample {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,                    // 核心线程数
            5,                    // 最大线程数
            60L,                  // 空闲线程存活时间
            TimeUnit.SECONDS,     // 时间单位
            new ArrayBlockingQueue<>(10),  // 任务队列
            new ThreadPoolExecutor.AbortPolicy()  // 拒绝策略
        );
        
        executor.execute(() -> System.out.println("任务执行中"));
        executor.shutdown();
    }
}

四种拒绝策略对比:

当线程池的任务队列已满且线程数达到最大值时,新提交的任务会触发拒绝策略。ThreadPoolExecutor 提供了四种内置的拒绝策略:

拒绝策略 行为 适用场景 注意事项
AbortPolicy(默认) 直接抛出 RejectedExecutionException 异常 任务不允许丢失、需要及时感知提交失败的场景 默认策略,异常需调用方捕获处理,否则可能导致任务中断
CallerRunsPolicy 由提交任务的线程(调用者)自己执行该任务 希望降低任务丢弃率、且能接受任务在调用线程中执行的场景 会阻塞调用线程,可能影响调用方的响应速度,但不会丢失任务
DiscardPolicy 静默丢弃被拒绝的任务,不抛异常 允许丢弃部分任务、对任务完整性要求不高的场景 任务被静默丢弃,无任何提示,可能造成数据丢失
DiscardOldestPolicy 丢弃队列中最旧(队首)的任务,然后重新尝试提交新任务 追求最新任务优先执行、可接受丢弃旧任务的场景 会丢弃队列中等待最久的任务,适合消息时效性要求高的场景

代码示例:

java 复制代码
import java.util.concurrent.*;

public class RejectedPolicyExample {

    public static void main(String[] args) {
        // 1. AbortPolicy:默认策略,队列满时直接抛出异常
        ThreadPoolExecutor abortExecutor = new ThreadPoolExecutor(
            1, 1, 0L, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(1),
            new ThreadPoolExecutor.AbortPolicy()
        );
        try {
            abortExecutor.execute(() -> System.out.println("任务1执行"));
            abortExecutor.execute(() -> System.out.println("任务2执行"));
            abortExecutor.execute(() -> System.out.println("任务3执行")); // 触发拒绝
        } catch (RejectedExecutionException e) {
            System.out.println("AbortPolicy:任务被拒绝,抛出异常:" + e.getMessage());
        } finally {
            abortExecutor.shutdown();
        }

        // 2. CallerRunsPolicy:由提交任务的线程(主线程)自己执行
        ThreadPoolExecutor callerRunsExecutor = new ThreadPoolExecutor(
            1, 1, 0L, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(1),
            new ThreadPoolExecutor.CallerRunsPolicy()
        );
        for (int i = 1; i <= 3; i++) {
            final int taskId = i;
            callerRunsExecutor.execute(() ->
                System.out.println("CallerRunsPolicy:任务" + taskId + "由" + Thread.currentThread().getName() + "执行"));
        }
        callerRunsExecutor.shutdown();

        // 3. DiscardPolicy:静默丢弃被拒绝的任务
        ThreadPoolExecutor discardExecutor = new ThreadPoolExecutor(
            1, 1, 0L, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(1),
            new ThreadPoolExecutor.DiscardPolicy()
        );
        for (int i = 1; i <= 3; i++) {
            final int taskId = i;
            discardExecutor.execute(() ->
                System.out.println("DiscardPolicy:任务" + taskId + "执行"));
        }
        discardExecutor.shutdown();
        System.out.println("DiscardPolicy:被拒绝的任务被静默丢弃,无异常抛出");

        // 4. DiscardOldestPolicy:丢弃队列中最旧的任务,再尝试提交新任务
        ThreadPoolExecutor discardOldestExecutor = new ThreadPoolExecutor(
            1, 1, 0L, TimeUnit.MILLISECONDS,
            new ArrayBlockingQueue<>(1),
            new ThreadPoolExecutor.DiscardOldestPolicy()
        );
        for (int i = 1; i <= 3; i++) {
            final int taskId = i;
            discardOldestExecutor.execute(() -> {
                try {
                    Thread.sleep(100);
                    System.out.println("DiscardOldestPolicy:任务" + taskId + "执行");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
        discardOldestExecutor.shutdown();
    }
}

选择建议:

  • 默认使用 AbortPolicy:让任务提交失败及时暴露,便于调用方感知并做补偿处理,是生产环境最稳妥的选择。
  • 对任务完整性要求高、可接受调用线程执行:使用 CallerRunsPolicy,通过调用方线程执行来降低任务丢弃率,但要注意调用线程可能被阻塞。
  • 允许丢弃部分任务、追求吞吐量:使用 DiscardPolicy,静默丢弃不重要的任务。
  • 消息时效性要求高、可接受丢弃旧任务:使用 DiscardOldestPolicy,优先保证最新任务被执行。

完整实战示例:

下面通过一个完整的实战示例,演示如何使用 ThreadPoolExecutor 手动创建线程池,并展示任务提交、执行结果获取和优雅关闭的完整流程:

java 复制代码
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadPoolPractice {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 1. 手动创建线程池,显式配置各项参数
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,                          // 核心线程数:常驻线程
            4,                          // 最大线程数:高峰期最多创建的线程数
            30L,                        // 空闲线程存活时间
            TimeUnit.SECONDS,           // 时间单位
            new ArrayBlockingQueue<>(3),// 有界任务队列:容量为 3
            new ThreadPoolExecutor.AbortPolicy()  // 拒绝策略:队列满且线程达上限时抛异常
        );

        // 2. 使用 AtomicInteger 统计成功执行的任务数
        AtomicInteger successCount = new AtomicInteger(0);

        // 3. 提交 6 个 Callable 任务,通过 Future 获取执行结果
        //    核心线程 2 个 + 队列 3 个 = 前 5 个任务可被接受,第 6 个任务触发拒绝策略
        Future<Integer>[] futures = new Future[6];
        for (int i = 1; i <= 6; i++) {
            final int taskId = i;
            try {
                futures[i - 1] = executor.submit(() -> {
                    System.out.println("任务 " + taskId + " 开始执行,线程:" 
                        + Thread.currentThread().getName());
                    Thread.sleep(500);  // 模拟耗时任务
                    System.out.println("任务 " + taskId + " 执行完成");
                    return taskId * 10; // 返回计算结果
                });
            } catch (RejectedExecutionException e) {
                System.out.println("任务 " + taskId + " 被拒绝:" + e.getMessage());
            }
        }

        // 4. 获取并汇总各任务的执行结果
        int total = 0;
        for (Future<Integer> future : futures) {
            if (future != null) {
                try {
                    total += future.get();  // 阻塞等待任务完成并获取结果
                    successCount.incrementAndGet();
                } catch (ExecutionException e) {
                    System.err.println("任务执行异常:" + e.getCause());
                }
            }
        }
        System.out.println("成功执行任务数:" + successCount.get() + ",结果总和:" + total);

        // 5. 优雅关闭线程池
        executor.shutdown();  // 不再接受新任务,等待已提交任务执行完毕
        try {
            // 最多等待 10 秒,超时则强制关闭
            if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
                System.out.println("任务未在限定时间内完成,强制关闭线程池");
                executor.shutdownNow();  // 强制中断未执行的任务
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
        System.out.println("线程池已优雅关闭,程序结束");
    }
}

代码要点说明:

  • 参数配置:核心线程数 2、最大线程数 4、有界队列容量 3,因此最多可同时容纳 5 个任务(2 个执行中 + 3 个排队),第 6 个任务会触发 AbortPolicy 拒绝策略。
  • 任务提交 :使用 submit(Callable) 提交带返回值的任务,返回 Future 对象用于获取执行结果。
  • 结果获取 :future.get() 会阻塞当前线程直到任务完成,可捕获 ExecutionException 处理任务内部异常。
  • 优雅关闭 :shutdown() 停止接收新任务但等待已提交任务完成,awaitTermination() 设置最长等待时间,超时后用 shutdownNow() 强制中断,避免线程池无法正常退出。

7. 并发工具类

7.1 CountDownLatch

用于等待多个线程完成任务:

java 复制代码
import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                System.out.println("子任务完成");
                latch.countDown();
            }).start();
        }
        
        latch.await();
        System.out.println("所有子任务已完成,主线程继续执行");
    }
}

7.2 CyclicBarrier

用于让一组线程互相等待:

java 复制代码
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, () -> {
            System.out.println("所有线程已到达屏障点");
        });
        
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                try {
                    System.out.println("线程准备就绪");
                    barrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

7.3 Semaphore

用于控制同时访问的线程数量:

java 复制代码
import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2);
        
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println("线程获取许可,执行任务");
                    Thread.sleep(1000);
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

8. 实战案例:多线程下载器

下面通过一个完整的实战案例,综合运用前面学到的知识:

java 复制代码
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class MultiThreadDownloader {
    private static final int THREAD_COUNT = 4;
    private static final AtomicInteger downloadedSize = new AtomicInteger(0);
    private static final int TOTAL_SIZE = 1000;
    
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        
        for (int i = 0; i < THREAD_COUNT; i++) {
            final int threadId = i;
            executor.execute(() -> {
                try {
                    // 模拟下载任务
                    int chunkSize = TOTAL_SIZE / THREAD_COUNT;
                    for (int j = 0; j < chunkSize; j++) {
                        downloadedSize.incrementAndGet();
                        Thread.sleep(1);
                    }
                    System.out.println("线程 " + threadId + " 下载完成");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }
        
        latch.await();
        executor.shutdown();
        System.out.println("下载完成,总大小:" + downloadedSize.get());
    }
}

9. 常见问题与最佳实践

9.1 死锁的避免

死锁是多线程编程中常见的问题,可以通过以下方式避免:

  • 按固定顺序获取锁。
  • 使用 tryLock 并设置超时时间。
  • 尽量减少锁的持有时间。

下面通过一个典型的死锁示例,直观展示两个线程互相持有对方所需锁的场景:

java 复制代码
public class DeadlockExample {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        // 线程 1:先持有 lockA,再尝试获取 lockB
        Thread thread1 = new Thread(() -> {
            synchronized (lockA) {
                System.out.println("线程1 持有 lockA,等待 lockB...");
                try { Thread.sleep(100); } catch (InterruptedException e) { }
                synchronized (lockB) {   // 此时线程2 已持有 lockB,线程1 阻塞等待
                    System.out.println("线程1 获取到 lockB");
                }
            }
        });

        // 线程 2:先持有 lockB,再尝试获取 lockA
        Thread thread2 = new Thread(() -> {
            synchronized (lockB) {
                System.out.println("线程2 持有 lockB,等待 lockA...");
                try { Thread.sleep(100); } catch (InterruptedException e) { }
                synchronized (lockA) {   // 此时线程1 已持有 lockA,线程2 阻塞等待
                    System.out.println("线程2 获取到 lockA");
                }
            }
        });

        thread1.start();
        thread2.start();
        // 两个线程互相等待对方释放锁,程序将永远卡住,形成死锁
    }
}

如何避免死锁?

方式一:按固定顺序获取锁

让所有线程都按照相同的顺序(如先 lockA 后 lockB)获取锁,即可打破循环等待:

java 复制代码
public class FixedOrderExample {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lockA) {
                synchronized (lockB) {
                    System.out.println("线程1 按固定顺序获取锁成功");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lockA) {   // 与线程1 保持相同的加锁顺序
                synchronized (lockB) {
                    System.out.println("线程2 按固定顺序获取锁成功");
                }
            }
        });

        thread1.start();
        thread2.start();
        // 两个线程都先获取 lockA 再获取 lockB,永远不会出现互相等待
    }
}

方式二:使用 tryLock 并设置超时时间

使用 Lock 接口的 tryLock 方法,在指定时间内获取不到锁就放弃,避免无限期等待:

java 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class TryLockExample {
    private static final Lock lockA = new ReentrantLock();
    private static final Lock lockB = new ReentrantLock();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            try {
                // 尝试获取 lockA,最多等待 1 秒
                if (lockA.tryLock(1, TimeUnit.SECONDS)) {
                    try {
                        System.out.println("线程1 获取 lockA");
                        Thread.sleep(100);
                        // 尝试获取 lockB,获取不到则放弃,避免死锁
                        if (lockB.tryLock(1, TimeUnit.SECONDS)) {
                            try {
                                System.out.println("线程1 获取 lockB,执行任务");
                            } finally {
                                lockB.unlock();
                            }
                        } else {
                            System.out.println("线程1 获取 lockB 超时,放弃执行");
                        }
                    } finally {
                        lockA.unlock();
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        Thread thread2 = new Thread(() -> {
            try {
                if (lockB.tryLock(1, TimeUnit.SECONDS)) {
                    try {
                        System.out.println("线程2 获取 lockB");
                        Thread.sleep(100);
                        if (lockA.tryLock(1, TimeUnit.SECONDS)) {
                            try {
                                System.out.println("线程2 获取 lockA,执行任务");
                            } finally {
                                lockA.unlock();
                            }
                        } else {
                            System.out.println("线程2 获取 lockA 超时,放弃执行");
                        }
                    } finally {
                        lockB.unlock();
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        thread1.start();
        thread2.start();
        // tryLock 超时后主动放弃,线程不会无限期阻塞,从而避免死锁
    }
}

关键点总结:

  • 死锁产生的四个必要条件:互斥、持有并等待、不可剥夺、循环等待,打破任意一个即可避免死锁。
  • 固定锁顺序:让所有线程按相同顺序加锁,直接消除循环等待。
  • tryLock 超时:获取不到锁时主动放弃并释放已持有的锁,避免无限期阻塞。
  • 尽量减少锁的持有时间:只在临界区持有锁,不要在持锁时执行耗时操作。

9.3 原子类与锁的性能对比

在高并发自增场景下,AtomicInteger 与 synchronized 的性能差异非常明显。下面通过一个简单的基准测试来直观对比:

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.CountDownLatch;

public class AtomicVsSyncBenchmark {
    private static final int THREAD_COUNT = 8;
    private static final int INCREMENT_COUNT = 1_000_000;

    public static void main(String[] args) throws InterruptedException {
        // 方式一:使用 AtomicInteger(CAS 无锁)
        AtomicInteger atomicCount = new AtomicInteger(0);
        long atomicTime = runWithAtomic(atomicCount);
        System.out.println("AtomicInteger 耗时:" + atomicTime + " ms,结果:" + atomicCount.get());

        // 方式二:使用 synchronized(悲观锁)
        SyncCounter syncCounter = new SyncCounter();
        long syncTime = runWithSynchronized(syncCounter);
        System.out.println("synchronized 耗时:" + syncTime + " ms,结果:" + syncCounter.getCount());
    }

    private static long runWithAtomic(AtomicInteger count) throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < INCREMENT_COUNT; j++) {
                    count.incrementAndGet();   // CAS 自旋,无锁竞争
                }
                latch.countDown();
            }).start();
        }
        latch.await();
        return System.currentTimeMillis() - start;
    }

    private static long runWithSynchronized(SyncCounter counter) throws InterruptedException {
        long start = System.currentTimeMillis();
        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                for (int j = 0; j < INCREMENT_COUNT; j++) {
                    counter.increment();       // 每次自增都需要获取锁
                }
                latch.countDown();
            }).start();
        }
        latch.await();
        return System.currentTimeMillis() - start;
    }

    static class SyncCounter {
        private int count = 0;
        public synchronized void increment() {
            count++;
        }
        public synchronized int getCount() {
            return count;
        }
    }
}

性能差异分析:

  • AtomicInteger:基于 CAS(Compare-And-Swap)无锁实现,自增操作在 CPU 层面完成,没有线程阻塞和上下文切换,高并发下性能通常优于 synchronized。
  • synchronized:属于悲观锁,每次自增都需要获取和释放锁,存在锁竞争、线程阻塞和上下文切换的开销,并发越高开销越大。

选择建议:

  • 简单计数器、累加器、统计类场景 :优先使用 AtomicInteger、AtomicLong 等原子类,性能更好且代码简洁。
  • 需要保护多个变量或复杂业务逻辑 :使用 synchronized 或 Lock,因为原子类只能保证单个变量的原子性,无法保证复合操作的原子性。
  • 读多写少、对一致性要求极高的场景 :可结合 volatile 保证可见性,配合原子类或锁保证原子性。

9.2 线程安全集合

优先使用 java.util.concurrent 包下的线程安全集合:

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • BlockingQueue

9.3 最佳实践总结

  • 优先使用线程池而非手动创建线程。
  • 使用原子类替代简单的同步操作。
  • 合理设置线程池参数,避免资源浪费。
  • 使用 volatile 保证可见性,使用 synchronized 或 Lock 保证原子性。
  • 避免在持有锁时执行耗时操作。

10. 总结与展望

10.1 核心知识点回顾

本文系统性地梳理了 Java 多线程编程的完整知识体系,核心知识点可归纳为以下五个层面:

  • 线程基础:线程是 CPU 调度的基本单位,与进程共享地址空间;线程在其生命周期中经历 NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED 六种状态,理解状态转换是并发编程的基石。
  • 创建方式 :继承 Thread、实现 Runnable、使用 Callable + Future 三种方式,推荐优先使用接口实现以保留类的继承能力,并通过 Future 获取异步执行结果。
  • 同步机制 :synchronized 提供内置互斥,Lock 接口更灵活(支持 tryLock、公平锁),volatile 保证可见性但不保证原子性,原子类基于 CAS 实现无锁自增。
  • 线程池 :通过复用线程降低创建销毁开销,推荐使用 ThreadPoolExecutor 手动创建,显式配置核心线程数、最大线程数、队列容量和拒绝策略,避免 Executors 便捷方法的无界队列与线程数失控隐患。
  • 并发工具类 :CountDownLatch 用于等待多个任务完成,CyclicBarrier 让一组线程互相等待,Semaphore 控制同时访问的线程数量,配合线程安全集合(ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue)应对各类并发场景。

10.2 进阶方向展望

掌握上述基础后,可以进一步探索以下多线程编程的进阶方向:

  • CompletableFuture :基于 Future 的增强,支持异步任务的链式编排、组合与回调,通过 thenApply、thenCombine、allOf 等方法优雅地处理复杂的异步流程,是函数式异步编程的核心工具。
  • Fork/Join 框架:采用分治思想,将大任务递归拆分为小任务并行执行,再合并结果,适合计算密集型任务(如大数据排序、矩阵运算),配合工作窃取算法提升 CPU 利用率。
  • 虚拟线程(Virtual Threads):JDK 21 正式引入的轻量级线程,由 JVM 调度而非操作系统,创建成本极低,可支撑百万级并发,大幅简化高并发 IO 密集型应用的编写,是未来 Java 并发编程的重要方向。

10.3 常见并发问题的排查建议

在实际开发中,遇到并发问题时可参考以下排查思路:

  • 死锁(Deadlock) :表现为程序卡死、线程互相等待对方持有的锁。排查时先用 jstack 导出线程转储,定位处于 BLOCKED 状态且互相等待的线程;修复时按固定顺序获取锁、使用 tryLock 设置超时,或尽量减少锁的持有时间,打破循环等待。
  • 活锁(Livelock):线程并未阻塞,却因互相谦让而不断改变状态、始终无法推进。排查时观察线程是否反复执行相同的让步逻辑却无实际进展;修复时引入随机退避或优先级策略,避免线程无限循环地互相让步。
  • 线程饥饿(Starvation) :低优先级线程长期得不到 CPU 时间或锁资源,导致任务迟迟无法执行。排查时结合线程转储观察是否有线程长期处于 RUNNABLE 却未获得调度;修复时使用公平锁(如 ReentrantLock(true))保证等待最久的线程优先获得锁,并合理设置线程优先级。

多线程编程是一个理论与实践并重的领域,建议读者在理解原理的基础上,多动手编写并发代码、善用 jstack 等诊断工具,逐步提升自己的并发编程与问题排查能力。

相关推荐
LMT_Lanmantech1 小时前
Creo许可降本增效:不增购,也能让现有许可发挥更大价值
java·大数据·开发语言
Shaoshing1 小时前
Serializable
java
wuminyu1 小时前
ZGC染色指针在虚拟线程stackChunkOop上的并发屏障全景分析
java·linux·c语言·jvm·c++
蜗牛互联网1 小时前
Python Agent 工具调用:审批令牌、参数白名单和累计预算
java·开发语言·人工智能·windows·后端·python
哦虎!1 小时前
【JavaEE】网络编程套接字
java
Wang's Blog1 小时前
Java 项目实战: 交友项目 - SpringCloud 整合 Dubbo 的方案概述
java·spring cloud·交友
imDwAaY2 小时前
程序里的地址是真实内存地址吗?从虚拟内存到地址转换
java·linux·服务器
承渊政道2 小时前
【Python编程—从入门到实践】(让程序记得、恢复并解释失败:Python 文件、异常与JSON系统教程)
开发语言·python·pycharm·json·异常处理
秋饼2 小时前
Claude Haiku 5.5 + Spring AI 2.0 企业级低成本高并发 AI Agent 实战:子智能体分工、成本熔断与百万上下文落地
java·ai·技术分享·后端开发