一篇讲清楚 Java 并发编程

前言

并发编程是 Java 开发中不可或缺的技能,也是面试中的高频考点。本文将系统性地讲解 Java 并发编程的核心知识体系,帮助你建立完整的并发编程知识框架。


一、并发基础概念

1.1 进程 vs 线程

特性 进程 线程
定义 操作系统资源分配的基本单位 CPU 调度和执行的基本单位
资源占用 独立内存空间 共享进程资源
通信 IPC(管道、消息队列等) 共享内存、通信简单
切换开销

1.2 并发 vs 并行

  • 并发:同一时间段内,多个任务都在进行(时间片轮转)
  • 并行:同一时刻,多个任务同时进行(多核 CPU)

1.3 线程状态

public enum State {
    NEW,           // 新建状态
    RUNNABLE,      // 可运行状态
    BLOCKED,       // 阻塞状态
    WAITING,       // 等待状态
    TIMED_WAITING, // 超时等待状态
    TERMINATED     // 终止状态
}

二、线程的创建与使用

2.1 创建线程的四种方式

方式一:继承 Thread 类
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程运行中");
    }
}

// 使用
MyThread thread = new MyThread();
thread.start();
方式二:实现 Runnable 接口
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程运行中");
    }
}

// 使用
Thread thread = new Thread(new MyRunnable());
thread.start();
方式三:实现 Callable 接口(有返回值)
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "线程执行结果";
    }
}

// 使用
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get(); // 阻塞获取结果
方式四:使用线程池(推荐)
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
    System.out.println("线程池执行任务");
});
executor.shutdown();

2.2 线程的优先级

thread.setPriority(Thread.MAX_PRIORITY); // 10
thread.setPriority(Thread.MIN_PRIORITY); // 1
thread.setPriority(Thread.NORM_PRIORITY); // 5(默认)

三、Synchronized 关键字

3.1 三种使用方式

实例方法锁
public class Counter {
    private int count = 0;

    // 锁住的是 this 对象
    public synchronized void increment() {
        count++;
    }
}
静态方法锁
public class Counter {
    private static int count = 0;

    // 锁住的是 Class 对象
    public static synchronized void increment() {
        count++;
    }
}
代码块锁
public class Counter {
    private int count = 0;
    private final Object lock = new Object();

    public void increment() {
        synchronized (lock) { // 锁住指定的对象
            count++;
        }
    }
}

3.2 Synchronized 原理

  • Monitor 监视器:每个对象都有一个 Monitor
  • 字节码层面monitorentermonitorexit
  • 锁升级过程:偏向锁 → 轻量级锁 → 重量级锁

四、Volatile 关键字

4.1 作用

public class VolatileExample {
    // 保证可见性和有序性,但不保证原子性
    private volatile boolean flag = false;

    public void setFlag() {
        flag = true; // 立即对其他线程可见
    }

    public void checkFlag() {
        while (!flag) {
            // 等待 flag 变为 true
        }
    }
}

4.2 Volatile vs Synchronized

特性 Volatile Synchronized
可见性
有序性
原子性
阻塞

五、CAS 与 AQS

5.1 CAS(Compare And Swap)

// CAS 原理示例(伪代码)
public boolean compareAndSet(int expect, int update) {
    if (value == expect) {
        value = update;
        return true;
    }
    return false;
}

ABA 问题:解决方式是使用版本号 AtomicStampedReference

5.2 AQS(AbstractQueuedSynchronizer)

AQS 是 Java 并发包的基石,核心思想:

  • 状态变量volatile int state
  • CLH 队列:等待线程的队列
  • 独占/共享:两种模式
// ReentrantLock 基于 AQS 实现
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区代码
} finally {
    lock.unlock();
}

六、并发工具类

6.1 CountDownLatch(倒计时门闩)

CountDownLatch latch = new CountDownLatch(3);

// 启动 3 个线程
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            // 执行任务
        } finally {
            latch.countDown(); // 计数减 1
        }
    }).start();
}

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

6.2 CyclicBarrier(循环栅栏)

CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到达屏障点");
});

for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            // 执行任务
            barrier.await(); // 等待其他线程
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
}

6.3 Semaphore(信号量)

Semaphore semaphore = new Semaphore(5); // 允许 5 个线程同时访问

for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            semaphore.acquire(); // 获取许可
            // 访问共享资源
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release(); // 释放许可
        }
    }).start();
}

6.4 Exchanger(交换器)

Exchanger<String> exchanger = new Exchanger<>();

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

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

七、线程池

7.1 为什么使用线程池

  • 降低资源消耗:重复利用已创建的线程
  • 提高响应速度:任务到达时直接执行
  • 便于管理:统一分配、调优和监控

7.2 线程池核心参数

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    5,                          // corePoolSize: 核心线程数
    10,                         // maximumPoolSize: 最大线程数
    60L,                        // keepAliveTime: 非核心线程存活时间
    TimeUnit.SECONDS,           // 时间单位
    new LinkedBlockingQueue<>(100), // 工作队列
    Executors.defaultThreadFactory(),  // 线程工厂
    new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

7.3 四种拒绝策略

策略 说明
AbortPolicy(默认) 抛出异常
CallerRunsPolicy 调用者所在线程执行
DiscardPolicy 直接丢弃
DiscardOldestPolicy 丢弃队列最前面的任务

7.4 合理配置线程池

// CPU 密集型
int n = Runtime.getRuntime().availableProcessors();
poolSize = n + 1

// IO 密集型
poolSize = n * 2

7.5 禁止使用 Executors 创建

// 不推荐(OOM 风险)
ExecutorService executor1 = Executors.newFixedThreadPool(10);
ExecutorService executor2 = Executors.newCachedThreadPool();

// 推荐:明确参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(...);

八、锁框架

8.1 ReentrantLock vs Synchronized

特性 ReentrantLock Synchronized
可中断
公平锁 ✗(非公平)
超时获取
条件变量 支持多个 单一
自动释放 手动 自动

8.2 ReadWriteLock(读写锁)

ReadWriteLock rwLock = new ReentrantReadWriteLock();

// 读锁(共享锁)
rwLock.readLock().lock();
try {
    // 读操作
} finally {
    rwLock.readLock().unlock();
}

// 写锁(排他锁)
rwLock.writeLock().lock();
try {
    // 写操作
} finally {
    rwLock.writeLock().unlock();
}

8.3 StampedLock(印章锁)

StampedLock lock = new StampedLock();

// 乐观读
long stamp = lock.tryOptimisticRead();
// 读取数据
if (!lock.validate(stamp)) {
    // 验证失败,升级为悲观读
    stamp = lock.readLock();
    try {
        // 重新读取
    } finally {
        lock.unlockRead(stamp);
    }
}

九、并发容器

9.1 ConcurrentHashMap

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key", "value");

// Java 8+ 原子操作
map.computeIfAbsent("key", k -> "default");
map.merge("key", "value", (oldVal, newVal) -> oldVal + newVal);

9.2 CopyOnWriteArrayList

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("item"); // 写时复制

// 适合读多写少的场景
for (String item : list) {
    // 读取不需要加锁
}

9.3 BlockingQueue

// ArrayBlockingQueue: 有界队列
BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);

// LinkedBlockingQueue: 可选有界
BlockingQueue<String> queue2 = new LinkedBlockingQueue<>(100);

// PriorityBlockingQueue: 优先级队列
BlockingQueue<Integer> queue3 = new PriorityBlockingQueue<>();

// SynchronousQueue: 同步队列(不存储元素)
BlockingQueue<String> queue4 = new SynchronousQueue<>();

十、原子类

10.1 基本类型原子类

AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.incrementAndGet();    // ++i
atomicInt.getAndIncrement();    // i++
atomicInt.addAndGet(5);         // i += 5
atomicInt.compareAndSet(5, 10); // CAS

10.2 引用类型原子类

AtomicReference<User> userRef = new AtomicReference<>();
User user = new User("Alice");
userRef.set(user);
userRef.compareAndSet(user, newUser);

10.3 数组类型原子类

AtomicIntegerArray array = new AtomicIntegerArray(10);
array.getAndSet(0, 100);
array.incrementAndGet(1);

10.4 对象属性修改原子类

AtomicIntegerFieldUpdater<User> updater =
    AtomicIntegerFieldUpdater.newUpdater(User.class, "age");

User user = new User();
updater.getAndIncrement(user);

10.5 原子累加器(Java 8+)

LongAdder adder = new LongAdder();
adder.increment(); // 性能优于 AtomicLong

LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);
accumulator.accumulate(10);

十一、常见问题与解决方案

11.1 死锁

产生原因:多个线程互相持有对方需要的资源

避免方法

  1. 固定加锁顺序
  2. 使用 tryLock() 设置超时
  3. 使用死锁检测机制
// 超时获取锁
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
    try {
        if (lock2.tryLock(1, TimeUnit.SECONDS)) {
            try {
                // 执行业务
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}

11.2 活锁

现象:线程不断改变状态,但无法继续执行

解决:引入随机性或重试机制

11.3 线程安全问题排查

  1. 查看线程堆栈jstack <pid>
  2. 查看内存状态jmap -histo <pid>
  3. 使用工具:VisualVM、Arthas

十二、最佳实践

12.1 编码建议

  1. 同步块范围最小化:减少锁持有时间
  2. 使用线程局部变量ThreadLocal
  3. 避免使用 stop/suspend:使用 interrupt
  4. 及时释放锁:使用 try-finally

12.2 性能优化

  1. 减小锁粒度:分段锁(ConcurrentHashMap 思想)
  2. 读写分离:ReadWriteLock
  3. 无锁编程:CAS、原子类
  4. 协程:Java 21+ 虚拟线程

12.3 Java 21+ 虚拟线程

// Java 21+
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> {
        // 虚拟线程执行任务
    });
}

总结

Java 并发编程是一个庞大的知识体系,本文涵盖了从基础概念到高级特性的核心内容。学习并发编程的关键是:

  1. 理解基础:掌握线程、锁、内存模型等核心概念
  2. 多实践:通过代码加深理解
  3. 看源码:学习 JDK 并发包的实现原理
  4. 关注更新:Java 虚拟线程等新特性

希望这篇文章能帮助你系统地掌握 Java 并发编程!


参考资料

  • 《Java 并发编程的艺术》
  • 《深入理解 Java 虚拟机》
  • Java 并发包源码(JDK)
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐