Java多线程编程全面解析:从原理到实战

文章目录

    • 引言
    • 第一部分:多线程基础概念
      • [1.1 进程与线程的定义](#1.1 进程与线程的定义)
      • [1.2 并发与并行的本质区别](#1.2 并发与并行的本质区别)
      • [1.3 Java程序运行原理与JVM的多线程特性](#1.3 Java程序运行原理与JVM的多线程特性)
      • [1.4 多线程的应用场景](#1.4 多线程的应用场景)
    • 第二部分:线程的创建与生命周期
      • [2.1 创建线程的四种方式](#2.1 创建线程的四种方式)
        • [2.1.1 继承Thread类](#2.1.1 继承Thread类)
        • [2.1.2 实现Runnable接口](#2.1.2 实现Runnable接口)
        • [2.1.3 实现Callable接口(有返回值)](#2.1.3 实现Callable接口(有返回值))
        • [2.1.4 使用线程池(Executor框架)](#2.1.4 使用线程池(Executor框架))
      • [2.2 线程生命周期与六种状态](#2.2 线程生命周期与六种状态)
      • [2.3 start()与run()的区别](#2.3 start()与run()的区别)
      • [2.4 线程的常用方法](#2.4 线程的常用方法)
        • [2.4.1 sleep():线程休眠](#2.4.1 sleep():线程休眠)
        • [2.4.2 join():线程合并](#2.4.2 join():线程合并)
        • [2.4.3 yield():线程让步](#2.4.3 yield():线程让步)
        • [2.4.4 interrupt():线程中断](#2.4.4 interrupt():线程中断)
      • [2.5 优雅地停止线程](#2.5 优雅地停止线程)
    • 第三部分:线程同步与线程安全
      • [3.1 什么是线程安全?](#3.1 什么是线程安全?)
      • [3.2 synchronized关键字](#3.2 synchronized关键字)
        • [3.2.1 同步实例方法](#3.2.1 同步实例方法)
        • [3.2.2 同步静态方法](#3.2.2 同步静态方法)
        • [3.2.3 同步代码块](#3.2.3 同步代码块)
        • [3.2.4 案例:售票系统](#3.2.4 案例:售票系统)
        • [3.2.5 synchronized的原理](#3.2.5 synchronized的原理)
      • [3.3 volatile关键字](#3.3 volatile关键字)
      • [3.4 Lock接口与ReentrantLock](#3.4 Lock接口与ReentrantLock)
        • [3.4.1 基本用法](#3.4.1 基本用法)
        • [3.4.2 ReentrantLock的独特功能](#3.4.2 ReentrantLock的独特功能)
        • [3.4.3 synchronized与ReentrantLock对比](#3.4.3 synchronized与ReentrantLock对比)
      • [3.5 死锁及解决方案](#3.5 死锁及解决方案)
        • [3.5.1 什么是死锁?](#3.5.1 什么是死锁?)
        • [3.5.2 死锁示例](#3.5.2 死锁示例)
        • [3.5.3 避免死锁的策略](#3.5.3 避免死锁的策略)
      • [3.6 线程间通信](#3.6 线程间通信)
        • [3.6.1 wait/notify机制](#3.6.1 wait/notify机制)
        • [3.6.2 案例:交替打印](#3.6.2 案例:交替打印)
        • [3.6.3 sleep()与wait()的区别](#3.6.3 sleep()与wait()的区别)
        • [3.6.4 Condition接口](#3.6.4 Condition接口)
    • 第四部分:JUC高级并发工具
      • [4.1 线程池](#4.1 线程池)
        • [4.1.1 为什么要用线程池?](#4.1.1 为什么要用线程池?)
        • [4.1.2 ThreadPoolExecutor核心参数](#4.1.2 ThreadPoolExecutor核心参数)
        • [4.1.3 线程池执行流程](#4.1.3 线程池执行流程)
        • [4.1.4 线程池参数配置建议](#4.1.4 线程池参数配置建议)
        • [4.1.5 线程池状态](#4.1.5 线程池状态)
      • [4.2 原子类](#4.2 原子类)
      • [4.3 并发集合](#4.3 并发集合)
        • [4.3.1 ConcurrentHashMap的并发特性](#4.3.1 ConcurrentHashMap的并发特性)
        • [4.3.2 CopyOnWriteArrayList原理](#4.3.2 CopyOnWriteArrayList原理)
      • [4.4 同步工具类](#4.4 同步工具类)
        • [4.4.1 CountDownLatch(倒计数器)](#4.4.1 CountDownLatch(倒计数器))
        • [4.4.2 CyclicBarrier(循环屏障)](#4.4.2 CyclicBarrier(循环屏障))
        • [4.4.3 Semaphore(信号量)](#4.4.3 Semaphore(信号量))
        • [4.4.4 Exchanger(交换器)](#4.4.4 Exchanger(交换器))
      • [4.5 ThreadLocal](#4.5 ThreadLocal)
        • [4.5.1 基本用法](#4.5.1 基本用法)
        • [4.5.2 应用场景](#4.5.2 应用场景)
        • [4.5.3 内存泄漏问题](#4.5.3 内存泄漏问题)
    • 第五部分:多线程最佳实践与常见问题
      • [5.1 线程池使用规范](#5.1 线程池使用规范)
      • [5.2 如何选择合适的锁机制?](#5.2 如何选择合适的锁机制?)
      • [5.3 常见问题诊断](#5.3 常见问题诊断)
        • [5.3.1 如何定位死锁?](#5.3.1 如何定位死锁?)
        • [5.3.2 线程池阻塞问题](#5.3.2 线程池阻塞问题)
        • [5.3.3 性能分析工具](#5.3.3 性能分析工具)
      • [5.4 多线程设计原则](#5.4 多线程设计原则)
    • 结语

引言

在当今多核处理器普及的时代,多线程编程已经成为Java开发者必须掌握的核心技能。无论是高并发的Web服务、大数据处理,还是实时响应式应用,多线程技术都扮演着至关重要的角色。然而,多线程编程也是一把双刃剑:运用得当可以大幅提升系统性能,使用不当则可能导致数据错乱、死锁甚至系统崩溃。

本文将从基础概念入手,循序渐进地深入Java多线程的核心原理,通过丰富的代码案例和实践经验,帮助读者全面掌握Java多线程编程技术。全文约9000字,建议配合实际编码练习,以达到最佳学习效果。


第一部分:多线程基础概念

1.1 进程与线程的定义

在操作系统中,进程是资源分配的基本单位,它是一个正在执行的程序,包含了代码、数据和系统资源。每个进程拥有独立的地址空间,进程之间相互隔离。

线程则是操作系统能够进行运算调度的最小单位,它包含在进程之中,共享进程的资源(如内存、文件等)。一个进程可以包含多个线程,这些线程可以并行执行不同的任务。

打个比方:如果把进程比作一个工厂,那么线程就是工厂里的工人。工厂(进程)提供场地和设备(资源),工人们(线程)共享这些资源协同完成工作。

1.2 并发与并行的本质区别

这是初学者最容易混淆的概念。简单来说:

  • 并行 :多个任务在同一时刻真正同时执行。这需要多核CPU的支持,每个核心独立处理一个任务。
  • 并发 :多个任务在同一时间段内交替执行。在单核CPU上,通过快速的任务切换(时间片轮转),让用户感觉任务在同时进行,但实际上任一时刻只有一个任务在执行。

形象理解

  • 并行:四个厨师各做一道菜,真正同时进行。
  • 并发:一个厨师在煮汤、煎肉饼、切沙拉之间快速切换,看起来像同时在做多件事。

为什么这很重要?

理解并发与并行的区别有助于合理设计多线程程序。如果你的任务是CPU密集型(需要大量计算),那么多线程在单核CPU上不会带来性能提升,反而可能因线程切换增加开销。而如果是I/O密集型(等待网络、磁盘),即使单核也能通过并发提高资源利用率。

1.3 Java程序运行原理与JVM的多线程特性

很多人以为Java程序是单线程的,其实不然。当我们执行java命令启动一个Java程序时,JVM会启动多个线程,至少包括:

  • 主线程 :执行main方法的线程。
  • 垃圾回收线程:负责内存回收的后台线程。
  • 其他守护线程:如编译优化线程等。

面试高频题:JVM的启动是多线程的吗?
答案:是的,JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。

1.4 多线程的应用场景

多线程并非万能,但在以下场景中不可或缺:

  1. 服务器并发处理:Web服务器同时处理多个客户端请求。
  2. 响应式界面:教学控屏软件同时向多台电脑共享屏幕。
  3. 大数据处理:分析日志、统计海量数据。
  4. 异步任务:在不阻塞主线程的情况下执行耗时操作(如文件下载)。

第二部分:线程的创建与生命周期

2.1 创建线程的四种方式

2.1.1 继承Thread类

这是最直接的方式,但受限于Java的单继承特性。

java 复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 正在执行");
    }
}

public class Demo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();  // 启动新线程
    }
}

优点 :可以直接使用Thread类中的方法,代码简单。
缺点:如果类已经有父类,就不能使用这种方法。

2.1.2 实现Runnable接口

更推荐的方式,因为Java支持接口的多实现。

java 复制代码
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 正在执行");
    }
}

public class Demo {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
    }
}

优点 :不影响类的继承关系,多个线程可以共享同一个Runnable实例。
缺点:不能直接使用Thread中的方法,需要先获取线程对象。

2.1.3 实现Callable接口(有返回值)

如果需要线程执行后返回结果,可以使用Callable。

java 复制代码
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

public class Demo {
    public static void main(String[] args) throws Exception {
        FutureTask<Integer> task = new FutureTask<>(new MyCallable());
        new Thread(task).start();
        
        Integer result = task.get();  // 获取线程执行结果(会阻塞)
        System.out.println("计算结果:" + result);
    }
}

Callable与Runnable的区别:

  • Runnable的run()无返回值,不能抛出受检异常。
  • Callable的call()有返回值,可以抛出异常。
2.1.4 使用线程池(Executor框架)

这是企业级开发中最常用的方式,避免频繁创建销毁线程的开销。

java 复制代码
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
    executor.submit(() -> {
        System.out.println(Thread.currentThread().getName() + " 执行任务");
    });
}
executor.shutdown();

2.2 线程生命周期与六种状态

Java线程的生命周期由Thread.State枚举定义了6种状态:

复制代码
NEW → RUNNABLE → BLOCKED/WAITING/TIMED_WAITING → TERMINATED
状态 说明
NEW 线程刚创建,尚未调用start()
RUNNABLE 可运行状态,包含就绪和运行两种子状态
BLOCKED 阻塞状态,等待获取监视器锁
WAITING 无限期等待,需被显式唤醒
TIMED_WAITING 限期等待,时间到自动唤醒
TERMINATED 线程执行完毕

可以通过Thread.getState()方法实时监控线程状态。

2.3 start()与run()的区别

这是面试中的经典问题。看下面的代码:

java 复制代码
Thread t = new Thread(() -> System.out.println("线程执行"));
t.run();   // 不会启动新线程,只是普通方法调用
t.start(); // 启动新线程,JVM自动调用run()

核心区别

  • start()会启动新线程,由新线程执行run()中的代码。
  • run()只是普通方法调用,在调用者线程中执行,不会创建新线程。

2.4 线程的常用方法

2.4.1 sleep():线程休眠

让当前线程暂停执行指定时间,但不释放锁

java 复制代码
try {
    Thread.sleep(2000);  // 休眠2秒
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 恢复中断状态
}

关于sleep,有一个重要观点:谁执行不重要,重要的是在哪里执行 。无论通过Thread.sleep()还是t.sleep()调用,总是让当前正在执行的线程休眠,与调用对象无关。

2.4.2 join():线程合并

等待目标线程终止后再继续执行。

java 复制代码
Thread t = new Thread(() -> {
    // 执行耗时操作
});
t.start();
t.join();  // 当前线程等待t执行完毕
System.out.println("t已完成");
2.4.3 yield():线程让步

提示调度器当前线程愿意让出CPU,但调度器可能忽略。

2.4.4 interrupt():线程中断

中断线程,设置中断标志。但线程需要自己检查中断标志并响应。

2.5 优雅地停止线程

绝不推荐使用stop()方法 ,它已被废弃。因为stop()会强制终止线程,可能导致锁无法释放、数据不一致等问题。

正确方式:使用中断机制配合标志位。

java 复制代码
class MyThread extends Thread {
    private volatile boolean stopped = false;
    
    @Override
    public void run() {
        while (!stopped && !Thread.currentThread().isInterrupted()) {
            try {
                // 执行任务
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // 在阻塞时被中断,退出循环
                break;
            }
        }
    }
    
    public void shutdown() {
        stopped = true;
        this.interrupt();
    }
}

第三部分:线程同步与线程安全

3.1 什么是线程安全?

当多个线程访问共享资源时,如果没有采取同步措施,可能会导致数据不一致或错误的状态,这就是线程不安全。

经典问题:多个线程同时执行count++,最终结果可能小于预期,因为count++不是原子操作(包含读、加、写三步)。

3.2 synchronized关键字

synchronized是Java最基础的同步机制,确保同一时刻只有一个线程执行特定代码块。

3.2.1 同步实例方法

锁对象是当前实例(this)。

java 复制代码
public synchronized void increment() {
    count++;
}
3.2.2 同步静态方法

锁对象是类对象(ClassName.class)。

java 复制代码
public static synchronized void staticMethod() { }
3.2.3 同步代码块

可以指定锁对象,粒度更细。

java 复制代码
public void increment() {
    synchronized (this) {
        count++;
    }
}
3.2.4 案例:售票系统
java 复制代码
class TicketWindow implements Runnable {
    private static int tickets = 100;
    private Object lock = new Object();
    
    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(50); // 模拟出票耗时
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() 
                                       + " 卖出第 " + tickets-- + " 张票");
                } else {
                    break;
                }
            }
        }
    }
}

关键点 :多个同步代码块必须使用同一个锁对象才能实现同步。

3.2.5 synchronized的原理

在字节码层面,同步代码块前后会有monitorentermonitorexit指令。每个对象都有一个监视器锁(monitor),线程进入同步块前必须获得该锁,退出时释放。

3.3 volatile关键字

volatile是轻量级的同步机制,它保证:

  1. 可见性:一个线程修改变量后,其他线程立即看到最新值。
  2. 禁止指令重排序:阻止编译器对volatile变量前后的指令进行重排。

但它不保证原子性,适用于以下场景:

场景一:状态标记
java 复制代码
class Worker {
    private volatile boolean running = true;
    
    public void run() {
        while (running) {
            // 执行任务
        }
    }
    
    public void stop() {
        running = false;
    }
}
场景二:单例模式双检锁
java 复制代码
class Singleton {
    private static volatile Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(); // volatile防止指令重排
                }
            }
        }
        return instance;
    }
}

3.4 Lock接口与ReentrantLock

java.util.concurrent.locks.Lock提供了比synchronized更灵活的锁操作。

3.4.1 基本用法
java 复制代码
Lock lock = new ReentrantLock();

public void increment() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock(); // 必须释放锁
    }
}
3.4.2 ReentrantLock的独特功能
功能 说明
可中断锁 lock.lockInterruptibly()允许在等待锁时响应中断
可超时 lock.tryLock(3, TimeUnit.SECONDS)等待锁最多3秒
公平锁 new ReentrantLock(true)按请求顺序获取锁
多个条件变量 一个Lock可以创建多个Condition
3.4.3 synchronized与ReentrantLock对比
特性 synchronized ReentrantLock
使用方式 关键字,自动释放 API,需手动解锁
锁类型 非公平锁 可设置公平/非公平
可中断 不支持 支持lockInterruptibly()
超时 不支持 支持tryLock(timeout)
条件变量 一个锁一个等待集 多个Condition
性能 JDK6后优化较好 类似

3.5 死锁及解决方案

3.5.1 什么是死锁?

死锁是多个线程相互等待对方持有的资源,导致所有线程都无法继续执行的情况。

死锁产生的四个必要条件

  1. 互斥:资源一次只能被一个线程使用。
  2. 占有且等待:线程已持有一个资源,同时等待其他资源。
  3. 不可剥夺:资源只能由持有者主动释放。
  4. 循环等待:多个线程形成循环等待链。
3.5.2 死锁示例
java 复制代码
class DeadlockDemo {
    private static final Object lockA = new Object();
    private static final Object lockB = new Object();
    
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lockA) {
                System.out.println("线程1获得锁A");
                try { Thread.sleep(100); } catch (Exception e) {}
                synchronized (lockB) {
                    System.out.println("线程1获得锁B");
                }
            }
        }).start();
        
        new Thread(() -> {
            synchronized (lockB) {
                System.out.println("线程2获得锁B");
                synchronized (lockA) {
                    System.out.println("线程2获得锁A");
                }
            }
        }).start();
    }
}
3.5.3 避免死锁的策略
  1. 固定锁顺序:所有线程按相同顺序获取锁。
  2. 超时放弃 :使用tryLock(timeout),获取不到就放弃。
  3. 减少锁的粒度:尽量缩小同步块的范围。
  4. 使用并发工具 :如ConcurrentHashMap代替手动同步。

3.6 线程间通信

3.6.1 wait/notify机制

wait()notify()Object类的方法,用于线程间协作。它们必须在同步块中使用,且调用对象必须是同步锁对象。

为什么必须在同步块中调用?

  • 避免wait()notify()之间的竞态条件。
  • 确保调用线程持有该对象的锁。
3.6.2 案例:交替打印
java 复制代码
class Printer {
    private int flag = 1;
    
    public void printNumbers() throws InterruptedException {
        synchronized (this) {
            while (flag != 1) {
                this.wait();
            }
            System.out.print("1,2,3,4,5");
            flag = 2;
            this.notify(); // 唤醒等待的线程
        }
    }
    
    public void printLetters() throws InterruptedException {
        synchronized (this) {
            while (flag != 2) {
                this.wait();
            }
            System.out.print("A,B,C,D,E");
            flag = 1;
            this.notify();
        }
    }
}

注意wait()应在循环中检查条件,而不是if,防止虚假唤醒。

3.6.3 sleep()与wait()的区别
比较点 sleep() wait()
所属类 Thread类 Object类
是否释放锁
唤醒方式 时间到自动唤醒 notify/notifyAll或超时
使用位置 任何地方 必须在同步块中
3.6.4 Condition接口

与Lock配合使用,一个Lock可以创建多个Condition,实现更精细的线程控制。

java 复制代码
class BoundedBuffer {
    private final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    
    private final Object[] items = new Object[100];
    private int putIndex, takeIndex, count;
    
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) {
                notFull.await();
            }
            items[putIndex] = x;
            if (++putIndex == items.length) putIndex = 0;
            count++;
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
}

第四部分:JUC高级并发工具

4.1 线程池

4.1.1 为什么要用线程池?

频繁创建和销毁线程的开销很大,线程池通过复用线程来解决这个问题,同时还可以控制并发数量。

4.1.2 ThreadPoolExecutor核心参数
java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5,                     // corePoolSize: 核心线程数
    10,                    // maximumPoolSize: 最大线程数
    60, TimeUnit.SECONDS,  // keepAliveTime: 空闲线程存活时间
    new LinkedBlockingQueue<>(100),  // workQueue: 任务队列
    Executors.defaultThreadFactory(), // threadFactory: 线程工厂
    new ThreadPoolExecutor.AbortPolicy() // handler: 拒绝策略
);
4.1.3 线程池执行流程
  1. 如果线程数 < 核心线程数,创建新线程执行任务。
  2. 如果线程数 ≥ 核心线程数,任务加入队列等待。
  3. 如果队列已满且线程数 < 最大线程数,创建非核心线程执行任务。
  4. 如果队列已满且线程数 ≥ 最大线程数,执行拒绝策略。

拒绝策略

  • AbortPolicy:抛出异常(默认)
  • CallerRunsPolicy:调用者线程执行
  • DiscardPolicy:静默丢弃
  • DiscardOldestPolicy:丢弃最旧的任务
4.1.4 线程池参数配置建议

根据任务类型决定:

  • CPU密集型(大量计算):线程数 ≈ CPU核心数 + 1
  • IO密集型(等待网络/磁盘):线程数 ≈ CPU核心数 * 2
  • 混合型:通过压测确定最优值

生产环境:核心线程数与最大线程数通常保持一致,避免频繁创建销毁线程。

4.1.5 线程池状态
  • RUNNING:正常运行
  • SHUTDOWN:不再接收新任务,处理完队列任务
  • STOP:立即停止,中断正在执行的任务
  • TIDYING:所有任务终止,准备调用terminated()
  • TERMINATED:完全终止

4.2 原子类

java.util.concurrent.atomic包提供了一系列基于CAS(Compare and Swap)的原子类,如AtomicIntegerAtomicLong等。

java 复制代码
AtomicInteger counter = new AtomicInteger(0);

// 多个线程同时调用,保证原子性
counter.incrementAndGet(); // 相当于 i++

CAS原理:比较当前内存值与预期值,如果相等则更新为新值,整个过程是原子的。CAS是一种无锁算法,性能优于锁。

4.3 并发集合

集合类 适用场景
ConcurrentHashMap 高并发Map,替代Hashtable
CopyOnWriteArrayList 读多写少的List
BlockingQueue 生产者-消费者模式
ConcurrentLinkedQueue 高并发队列
4.3.1 ConcurrentHashMap的并发特性
  • 读操作无锁,写操作分段锁(Java 7)或CAS+synchronized(Java 8)。
  • 迭代器弱一致性,不会抛出ConcurrentModificationException
4.3.2 CopyOnWriteArrayList原理

写入时复制:写操作先复制数组,修改后再将原数组引用指向新数组。读操作不加锁,适合读多写少的场景。

4.4 同步工具类

4.4.1 CountDownLatch(倒计数器)

让一个或多个线程等待其他线程完成操作。

java 复制代码
CountDownLatch latch = new CountDownLatch(3);

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        // 执行任务
        latch.countDown(); // 计数器减1
    }).start();
}

latch.await(); // 等待计数器归零
System.out.println("所有任务完成");
4.4.2 CyclicBarrier(循环屏障)

多个线程互相等待,到达同一个屏障点后继续执行。

java 复制代码
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到达屏障,优先执行此任务");
});

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        // 执行部分任务
        barrier.await(); // 等待其他线程
        // 继续执行
    }).start();
}

与CountDownLatch的区别:CyclicBarrier可复用,CountDownLatch不可复用。

4.4.3 Semaphore(信号量)

控制同时访问资源的线程数量。

java 复制代码
Semaphore semaphore = new Semaphore(3); // 最多3个线程同时访问

for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire();
            // 访问共享资源
            Thread.sleep(1000);
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();
}
4.4.4 Exchanger(交换器)

用于两个线程交换数据。

java 复制代码
Exchanger<String> exchanger = new Exchanger<>();

new Thread(() -> {
    try {
        String data = exchanger.exchange("来自线程1的数据");
        System.out.println("线程1收到:" + data);
    } catch (InterruptedException e) {}
}).start();

new Thread(() -> {
    try {
        String data = exchanger.exchange("来自线程2的数据");
        System.out.println("线程2收到:" + data);
    } catch (InterruptedException e) {}
}).start();

4.5 ThreadLocal

ThreadLocal为每个线程提供独立的变量副本,线程之间互不影响。

4.5.1 基本用法
java 复制代码
ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

// 在线程A中
threadLocal.set(100);
Integer value = threadLocal.get(); // 100

// 在线程B中
Integer value = threadLocal.get(); // 0 (独立副本)
4.5.2 应用场景
  • 数据库连接管理(每个线程自己的Connection)
  • 用户会话信息(Web应用)
  • 事务上下文
4.5.3 内存泄漏问题

重要 :使用完ThreadLocal后,务必调用remove()方法,特别是在线程池环境中,否则可能因线程复用导致内存泄漏。

java 复制代码
try {
    threadLocal.set(value);
    // 业务逻辑
} finally {
    threadLocal.remove();
}

第五部分:多线程最佳实践与常见问题

5.1 线程池使用规范

阿里巴巴Java开发手册中关于线程池的规范:

【强制】 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让开发者更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池存在以下问题:

  • newFixedThreadPool:队列为LinkedBlockingQueue,容量无限,可能导致OOM。
  • newCachedThreadPool:最大线程数无限,可能导致创建过多线程。

5.2 如何选择合适的锁机制?

场景 推荐方案
简单的同步方法 synchronized
需要公平锁 ReentrantLock(true)
需要尝试获取锁 tryLock()
读多写少的共享数据 ReadWriteLock
简单计数器 AtomicInteger
高并发Map ConcurrentHashMap

5.3 常见问题诊断

5.3.1 如何定位死锁?
  1. 使用jstack命令查看线程堆栈。
  2. VisualVM等工具的可视化分析。
  3. 代码层面使用tryLock加超时避免死锁。
5.3.2 线程池阻塞问题
  • 队列过大导致内存压力。
  • 核心线程数设置过小,任务处理缓慢。
  • 任务内部死锁导致线程永久占用。
5.3.3 性能分析工具
  • JConsole:监控线程数、锁竞争。
  • VisualVM:分析CPU、内存、线程状态。
  • JProfiler:深入定位性能瓶颈。

5.4 多线程设计原则

  1. 优先使用并发工具而非手动同步。
  2. 最小化锁范围:只在必要时同步。
  3. 避免在锁中执行耗时操作
  4. 使用不可变对象,天然线程安全。
  5. 细粒度锁优于粗粒度锁。
  6. 警惕死锁,保持一致的锁顺序。

结语

多线程编程是Java开发中的深水区,既充满挑战也富有魅力。从基础的线程创建到高级的并发工具,从简单的同步机制到复杂的无锁算法,每一步都需要扎实的理论基础和丰富的实践经验。

本文系统地梳理了Java多线程的知识体系,涵盖了概念基础、线程控制、同步机制、并发工具以及最佳实践。希望读者能够结合实际项目,从简单的并发场景开始,逐步深入,最终掌握这门核心技能。

记住:多线程不是为了使用而使用,而是为了解决性能问题而使用的工具。在追求并发性能的同时,更要保证程序的正确性和可维护性。

相关推荐
Cosmoshhhyyy1 小时前
《Effective Java》解读第38条:用接口模拟可扩展的枚举
java·开发语言
wangbing11252 小时前
平台介绍-主数据系统-同步消息设计
java
小冷coding2 小时前
【Java】最新Java高并发高可用平台技术选型指南(思路+全栈路线)
java·开发语言
Jia ming2 小时前
《智能法官软件项目》—法律计算器模块
python·教学·案例·智能法官
爱华晨宇2 小时前
Python列表入门:常用操作与避坑指南
开发语言·windows·python
寻星探路2 小时前
【前端基础】HTML + CSS + JavaScript 快速入门(三):JS 与 jQuery 实战
java·前端·javascript·css·c++·ai·html
一切顺势而行2 小时前
python 面向对象
开发语言·python
cyforkk3 小时前
Tomcat 类加载机制解析:为何依赖包必须放在 WEB-INF/lib 目录下
java
绍兴贝贝3 小时前
代码随想录算法训练营第四十六天|LC647.回文子串|LC516.最长回文子序列|动态规划总结
数据结构·人工智能·python·算法·动态规划·力扣