mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
1154 字
3 分钟
Java多线程编程详解
2026-02-04

多线程是Java面试的重灾区,synchronized、volatile、Lock的区别能聊一个小时。说实话,我最早写多线程代码也是各种踩坑——死锁、可见性问题、线程安全,一个都没落下。这篇文章把多线程的知识点串了一遍,全是实战经验。

Java多线程编程详解#

线程 vs 进程#

  • 进程:操作系统分配资源的最小单位,每个进程有独立的内存空间
  • 线程:CPU调度的最小单位,同一进程内的线程共享内存

区别:进程之间相互隔离,一个进程崩了不影响其他进程;线程之间共享内存,一个线程崩了可能拖垮整个进程。

创建线程的四种方式#

1. 继承Thread类#

public class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行中: " + Thread.currentThread().getName());
}
}
// 使用
new MyThread().start(); // 注意是start(),不是run()!

:调run()不会启动新线程,只是在当前线程执行方法。我刚学的时候就犯过这个错——调了半天run()以为是并行的,结果发现一直在主线程里跑。

2. 实现Runnable接口(推荐)#

public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程运行中");
}
}
// 使用
new Thread(new MyRunnable()).start();
// Lambda更简洁
new Thread(() -> System.out.println("线程运行中")).start();

推荐理由:Java单继承,继承了Thread就不能继承别的类了。实现Runnable更灵活。

3. 实现Callable接口(带返回值)#

public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) sum += i;
return sum;
}
}
// 使用
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
Integer result = futureTask.get(); // 获取结果,会阻塞直到线程完成

4. 线程池(生产环境推荐)#

ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("任务 " + taskId + " 运行中");
});
}
executor.shutdown();

线程生命周期#

新建(New) → 就绪(Runnable) ↔ 运行(Running) → 终止(Terminated)
↓ ↑
阻塞/等待(Blocked/Waiting/Timed Waiting)
  • New:new了Thread对象,还没start
  • Runnable:调了start,等待CPU分配时间片
  • Running:正在执行
  • Blocked/Waiting:等待锁、等待通知、sleep等
  • Terminated:执行完毕

线程安全#

多个线程同时访问共享数据时,如果不加控制,就会出问题。典型的场景:两个线程同时给一个变量+1,结果只加了1次。

解决方案#

synchronized#

// 同步代码块
synchronized (lockObject) {
// 线程安全的操作
}
// 同步方法
public synchronized void method() {
// 线程安全的操作
}

Lock(更灵活)#

Lock lock = new ReentrantLock();
try {
lock.lock();
// 线程安全的操作
} finally {
lock.unlock(); // 一定要在finally里释放
}

synchronized vs Lock

  • synchronized是关键字,自动释放锁;Lock需要手动释放
  • Lock支持尝试获取锁(tryLock())、可中断等高级功能
  • 简单场景用synchronized,复杂场景用Lock

volatile —— 保证可见性#

private volatile boolean running = true;

volatile保证一个线程修改了变量,其他线程能立即看到。但它不保证原子性count++这种操作还是需要synchronized。

原子类#

AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子+1,线程安全

线程安全集合#

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

ThreadLocal —— 线程局部变量#

每个线程都有自己的副本,互不干扰:

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
threadLocal.set(100);
int value = threadLocal.get();
threadLocal.remove(); // 记得清理,防止内存泄漏!

线程通信#

wait/notify#

synchronized (lock) {
while (!condition) {
lock.wait(); // 释放锁,等待通知
}
// 条件满足,继续执行
lock.notify(); // 通知等待的线程
}

CountDownLatch —— 等待多个线程完成#

CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// 执行任务
latch.countDown(); // 完成一个,计数减1
}).start();
}
latch.await(); // 等待所有线程完成
System.out.println("所有任务完成");

CyclicBarrier —— 多个线程互相等待#

CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程都到达屏障点");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
barrier.await(); // 等待其他线程
// 继续执行
}).start();
}

CountDownLatch vs CyclicBarrier

  • CountDownLatch是”等别人做完”,CyclicBarrier是”互相等”
  • CountDownLatch只能用一次,CyclicBarrier可以重用

Semaphore —— 限流#

Semaphore semaphore = new Semaphore(2); // 同时最多2个线程执行
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire();
// 执行任务
} finally {
semaphore.release();
}
}).start();
}

线程池#

为什么用线程池?#

  • 减少线程创建和销毁的开销
  • 控制并发数,防止资源耗尽
  • 统一管理线程

创建线程池#

推荐用ThreadPoolExecutor手动创建,不要用Executors的快捷方法(newFixedThreadPool等可能因为队列无界导致OOM):

ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // corePoolSize:核心线程数
5, // maximumPoolSize:最大线程数
60, // keepAliveTime:空闲线程存活时间
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100), // 任务队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

执行流程:核心线程→任务队列→最大线程→拒绝策略

拒绝策略

  • AbortPolicy:抛异常(默认)
  • CallerRunsPolicy:调用者线程执行
  • DiscardPolicy:丢弃
  • DiscardOldestPolicy:丢弃最旧的任务

并发集合#

  • ConcurrentHashMap:线程安全的HashMap,高并发下比HashTable效率高多了
  • CopyOnWriteArrayList:读多写少场景,每次写操作复制整个数组
  • ConcurrentLinkedQueue:无界队列,高性能
  • BlockingQueue:阻塞队列,生产者-消费者模式的标配

实战贴士#

  1. 优先用线程池:不要手动new Thread,用线程池管理线程
  2. synchronized范围要小:只同步必要的代码块,不要同步整个方法
  3. Lock要在finally里释放:忘了unlock会导致死锁
  4. ThreadLocal用完要remove:在线程池环境下,线程复用会导致内存泄漏
  5. 并发集合代替手动同步:能用ConcurrentHashMap就别自己加synchronized

常见坑#

  • 调了run()没调start():没有启动新线程,代码在调用线程里执行。这个我刚学的时候就踩过。
  • 死锁:两个线程各自持有对方需要的锁,互相等待。用tryLock()可以避免。
  • ThreadLocal内存泄漏:线程池中的线程不会销毁,ThreadLocal的值一直存在,用完记得remove()
  • 线程池耗尽:任务队列无界,任务提交速度>处理速度,最终OOM。用有界队列。
  • 可见性问题:没有加volatile或synchronized,一个线程修改了变量,另一个线程看不到。
分享

如果这篇文章对你有帮助,欢迎分享给更多人!

部分信息可能已经过时

目录