多线程与并发
线程基础
进程和线程的区别?
答案:
| 特性 | 进程 | 线程 |
|---|---|---|
| 定义 | 资源分配的基本单位 | CPU调度的基本单位 |
| 资源 | 独立的内存空间 | 共享进程的内存空间 |
| 开销 | 创建、切换开销大 | 创建、切换开销小 |
| 通信 | 进程间通信(IPC) | 直接读写共享变量 |
| 影响 | 一个进程崩溃不影响其他进程 | 一个线程崩溃可能导致整个进程崩溃 |
创建线程的方式?
答案:
1. 继承Thread类
java
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行");
}
}
// 使用
MyThread thread = new MyThread();
thread.start();2. 实现Runnable接口(推荐)
java
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行");
}
}
// 使用
Thread thread = new Thread(new MyRunnable());
thread.start();
// Lambda方式
new Thread(() -> System.out.println("线程执行")).start();3. 实现Callable接口(有返回值)
java
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "执行结果";
}
}
// 使用
FutureTask<String> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
String result = task.get(); // 获取返回值4. 线程池(推荐)
java
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> System.out.println("线程执行"));
executor.shutdown();线程的生命周期?
答案:
NEW (新建)
↓ start()
RUNNABLE (就绪/运行)
↓ 获取锁失败 / wait() / sleep() / join()
BLOCKED / WAITING / TIMED_WAITING (阻塞/等待)
↓ 获取锁 / notify() / 超时
RUNNABLE
↓ run()执行完毕
TERMINATED (终止)状态说明:
- NEW:线程创建但未启动
- RUNNABLE:可运行状态(包括就绪和运行)
- BLOCKED:等待获取锁
- WAITING:无限期等待(wait、join)
- TIMED_WAITING:有限期等待(sleep、wait(timeout))
- TERMINATED:线程执行完毕
线程安全
什么是线程安全?如何保证?
答案: 多个线程访问同一资源时,不会出现数据不一致的问题。
保证线程安全的方式:
1. synchronized关键字
java
// 同步方法
public synchronized void method() {
// 线程安全的代码
}
// 同步代码块
public void method() {
synchronized (this) {
// 线程安全的代码
}
}
// 静态同步方法(锁Class对象)
public static synchronized void method() {
}2. Lock接口
java
private Lock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 线程安全的代码
} finally {
lock.unlock();
}
}3. volatile关键字(保证可见性,不保证原子性)
java
private volatile boolean flag = false;4. 原子类
java
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作5. ThreadLocal(线程隔离)
java
ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
userThreadLocal.set(user);
User user = userThreadLocal.get();synchronized和Lock的区别?
答案:
| 特性 | synchronized | Lock |
|---|---|---|
| 类型 | 关键字 | 接口 |
| 锁释放 | 自动释放 | 手动释放 |
| 获取锁 | 阻塞等待 | 可尝试获取(tryLock) |
| 公平性 | 非公平锁 | 可选公平锁 |
| 中断 | 不可中断 | 可中断(lockInterruptibly) |
| 条件变量 | 单个(wait/notify) | 多个(Condition) |
| 性能 | JDK6后优化,差距不大 | 稍高 |
java
// Lock的高级用法
ReentrantLock lock = new ReentrantLock(true); // 公平锁
// 尝试获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 业务代码
} finally {
lock.unlock();
}
}
// 可中断
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
// 被中断
}
// 多个条件变量
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();volatile关键字的作用?
答案:
两个作用:
- 保证可见性:一个线程修改变量,其他线程立即可见
- 禁止指令重排序:保证有序性
java
// 单例模式(双重检查锁)
public class Singleton {
private static volatile Singleton instance; // 必须用volatile
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 可能指令重排序
}
}
}
return instance;
}
}为什么需要volatile?
java
instance = new Singleton();
// 实际分为3步:
// 1. 分配内存
// 2. 初始化对象
// 3. 将instance指向内存
// 可能重排序为:1 -> 3 -> 2
// 导致其他线程获取到未初始化的对象注意:volatile不保证原子性
java
private volatile int count = 0;
public void increment() {
count++; // 不是原子操作,线程不安全
}
// 应该用AtomicInteger
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}线程池
线程池的核心参数?
答案:
java
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)参数说明:
- corePoolSize:核心线程数,即使空闲也不会销毁
- maximumPoolSize:最大线程数
- keepAliveTime:非核心线程空闲存活时间
- workQueue:任务队列(ArrayBlockingQueue、LinkedBlockingQueue等)
- threadFactory:创建线程的工厂
- handler:拒绝策略
线程池的执行流程?
答案:
1. 提交任务
↓
2. 核心线程数未满?
是 → 创建核心线程执行
否 ↓
3. 任务队列未满?
是 → 加入队列等待
否 ↓
4. 最大线程数未满?
是 → 创建非核心线程执行
否 ↓
5. 执行拒绝策略示例:
java
// 核心线程2,最大线程5,队列容量3
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3)
);
// 提交10个任务
for (int i = 0; i < 10; i++) {
executor.submit(() -> {});
}
// 执行流程:
// 任务1-2:创建核心线程执行
// 任务3-5:加入队列
// 任务6-8:创建非核心线程执行
// 任务9-10:执行拒绝策略线程池的拒绝策略?
答案:
| 策略 | 说明 |
|---|---|
| AbortPolicy(默认) | 抛出RejectedExecutionException |
| CallerRunsPolicy | 由调用线程执行任务 |
| DiscardPolicy | 直接丢弃任务 |
| DiscardOldestPolicy | 丢弃队列中最老的任务 |
java
// 自定义拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 5, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录日志、发送告警等
System.out.println("任务被拒绝");
}
}
);常见的线程池有哪些?
答案:
java
// 1. FixedThreadPool - 固定大小线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
// 2. CachedThreadPool - 缓存线程池(无界)
ExecutorService executor = Executors.newCachedThreadPool();
// 3. SingleThreadExecutor - 单线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
// 4. ScheduledThreadPool - 定时任务线程池
ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
executor.schedule(() -> {}, 1, TimeUnit.SECONDS); // 延迟1秒执行
executor.scheduleAtFixedRate(() -> {}, 0, 1, TimeUnit.SECONDS); // 每秒执行注意:阿里巴巴规范不推荐使用Executors创建线程池,因为:
- FixedThreadPool和SingleThreadExecutor:队列无界,可能OOM
- CachedThreadPool:线程数无界,可能OOM
推荐:手动创建ThreadPoolExecutor
并发工具类
CountDownLatch、CyclicBarrier、Semaphore的区别?
答案:
1. CountDownLatch(倒计时门栓)
- 一个或多个线程等待其他线程完成
- 计数器只能减,不能重置
java
CountDownLatch latch = new CountDownLatch(3);
// 3个工作线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("任务完成");
latch.countDown(); // 计数-1
}).start();
}
// 主线程等待
latch.await(); // 等待计数归零
System.out.println("所有任务完成");2. CyclicBarrier(循环栅栏)
- 多个线程互相等待,到达屏障点后一起执行
- 可以重复使用
java
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达屏障");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("到达屏障");
barrier.await(); // 等待其他线程
System.out.println("继续执行");
}).start();
}3. Semaphore(信号量)
- 控制同时访问资源的线程数量
java
Semaphore semaphore = new Semaphore(3); // 最多3个线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
semaphore.acquire(); // 获取许可
try {
System.out.println("执行任务");
} finally {
semaphore.release(); // 释放许可
}
}).start();
}AQS是什么?
答案: AQS (AbstractQueuedSynchronizer) 抽象队列同步器,是JUC并发包的基础框架。
核心思想:
- 使用int变量表示同步状态
- 使用FIFO队列管理等待线程
- 提供独占和共享两种模式
基于AQS实现的类:
- ReentrantLock
- Semaphore
- CountDownLatch
- ReentrantReadWriteLock
工作原理:
1. 线程尝试获取锁(修改state)
2. 获取失败,加入等待队列
3. 前驱节点释放锁,唤醒后继节点
4. 被唤醒的线程尝试获取锁练习题
- 什么是死锁?如何避免死锁?
- ThreadLocal的原理和应用场景?
- ConcurrentHashMap的实现原理?
- Fork/Join框架是什么?