一、前言:为什么多线程如此重要?
在现代软件开发中,多线程编程已成为必备的核心技能。随着多核处理器的普及和分布式系统的发展,能否编写高效、安全的并发程序直接决定了应用程序的性能和稳定性。Java作为企业级开发的主流语言,提供了丰富而强大的并发API,掌握这些技术不仅有助于日常开发,更是面试中的高频考点。
本文将系统性地讲解Java多线程的核心概念、实战技巧和面试要点,帮助读者构建完整的知识体系。
二、Java多线程基础概念
2.1 进程与线程的区别
-
进程:操作系统资源分配的基本单位,拥有独立的内存空间
-
线程:CPU调度的基本单位,共享进程资源,切换开销小
2.2 线程的创建方式
// 1. 继承Thread类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running");
}
}
// 2. 实现Runnable接口(推荐)
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}
// 3. 实现Callable接口(可获取返回值)
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}
// 4. 使用线程池(生产环境推荐)
ExecutorService executor = Executors.newFixedThreadPool(10);
2.3 线程生命周期
-
NEW:新建状态
-
RUNNABLE:可运行状态
-
BLOCKED:阻塞状态
-
WAITING:等待状态
-
TIMED_WAITING:超时等待状态
-
TERMINATED:终止状态
三、线程安全与同步机制
3.1 临界区与竞态条件
当多个线程同时访问共享资源时,如果没有正确的同步措施,会导致数据不一致问题。
3.2 synchronized关键字
// 同步方法
public synchronized void syncMethod() {
// 临界区代码
}
// 同步代码块
public void method() {
synchronized(this) {
// 临界区代码
}
}
// 静态方法同步
public static synchronized void staticSyncMethod() {
// 临界区代码
}
3.3 volatile关键字
保证变量的可见性和禁止指令重排序,但不保证原子性。
private volatile boolean flag = false;
3.4 Java锁体系
3.4.1 ReentrantLock
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock();
}
}
3.4.2 读写锁(ReadWriteLock)
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void read() {
rwLock.readLock().lock();
try {
// 读操作
} finally {
rwLock.readLock().unlock();
}
}
public void write() {
rwLock.writeLock().lock();
try {
// 写操作
} finally {
rwLock.writeLock().unlock();
}
}
四、Java并发工具类
4.1 CountDownLatch
等待多个线程完成后再执行主线程
CountDownLatch latch = new CountDownLatch(3);
// 工作线程
latch.countDown();
// 主线程
latch.await();
4.2 CyclicBarrier
让一组线程到达屏障点时被阻塞,直到最后一个线程到达
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程已就位");
});
// 线程中调用
barrier.await();
4.3 Semaphore
控制同时访问特定资源的线程数量
Semaphore semaphore = new Semaphore(5); // 允许5个线程同时访问
semaphore.acquire();
try {
// 使用资源
} finally {
semaphore.release();
}
4.4 Exchanger
两个线程间交换数据
Exchanger<String> exchanger = new Exchanger<>();
// 线程1
String data1 = exchanger.exchange("Data from thread1");
// 线程2
String data2 = exchanger.exchange("Data from thread2");
五、线程池深入解析
5.1 为什么使用线程池?
-
降低资源消耗:减少线程创建和销毁的开销
-
提高响应速度:任务到达时无需等待线程创建
-
提高线程可管理性:统一分配、监控和调优
5.2 ThreadPoolExecutor核心参数
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 空闲线程存活时间
unit, // 时间单位
workQueue, // 工作队列
threadFactory, // 线程工厂
handler // 拒绝策略
);
5.3 四种拒绝策略
-
AbortPolicy:抛出RejectedExecutionException(默认)
-
CallerRunsPolicy:由调用线程执行该任务
-
DiscardPolicy:直接丢弃任务
-
DiscardOldestPolicy:丢弃队列最前面的任务
5.4 Executors工具类提供的线程池
-
newFixedThreadPool:固定大小线程池
-
newCachedThreadPool:可缓存线程池
-
newSingleThreadExecutor:单线程线程池
-
newScheduledThreadPool:定时任务线程池
六、原子操作类
Java提供了java.util.concurrent.atomic包,解决原子性问题
// 原子整型
AtomicInteger atomicInt = new AtomicInteger(0);
atomicInt.incrementAndGet(); // 原子自增
// 原子引用
AtomicReference<String> atomicRef = new AtomicReference<>("初始值");
// CAS操作
boolean success = atomicInt.compareAndSet(expect, update);
七、面试常见问题与解答
7.1 基础概念类
Q1: synchronized和ReentrantLock的区别?
-
synchronized是JVM级别,ReentrantLock是API级别
-
ReentrantLock提供更灵活的锁机制:可中断、可轮询、定时锁等
-
synchronized会自动释放锁,ReentrantLock需要手动释放
Q2: volatile和synchronized的区别?
-
volatile保证可见性和有序性,不保证原子性
-
synchronized保证原子性、可见性和有序性
-
volatile不会造成线程阻塞,synchronized可能造成阻塞
7.2 实战应用类
Q3: 如何避免死锁?
-
避免一个线程同时获取多个锁
-
使用定时锁tryLock(timeout)
-
保持锁的获取顺序一致
-
使用较粗粒度的锁
Q4: 线程池的工作流程?
-
提交任务后,先判断核心线程是否已满
-
未满则创建新线程执行任务
-
已满则将任务放入工作队列
-
队列已满则判断线程数是否达到最大值
-
未达到则创建新线程,已达到则执行拒绝策略
7.3 高级特性类
Q5: ThreadLocal原理及应用场景?
-
每个线程有独立的ThreadLocalMap存储数据
-
应用场景:数据库连接、Session管理、参数传递等
-
注意内存泄漏问题,使用后及时remove()
Q6: AQS(AbstractQueuedSynchronizer)原理?
-
使用CLH队列管理获取锁的线程
-
通过state变量表示同步状态
-
提供了acquire()和release()等模板方法
846

被折叠的 条评论
为什么被折叠?



