Skip to content

多线程与并发

线程基础

进程和线程的区别?

答案:

特性进程线程
定义资源分配的基本单位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的区别?

答案:

特性synchronizedLock
类型关键字接口
锁释放自动释放手动释放
获取锁阻塞等待可尝试获取(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关键字的作用?

答案:

两个作用

  1. 保证可见性:一个线程修改变量,其他线程立即可见
  2. 禁止指令重排序:保证有序性
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. 被唤醒的线程尝试获取锁

练习题

  1. 什么是死锁?如何避免死锁?
  2. ThreadLocal的原理和应用场景?
  3. ConcurrentHashMap的实现原理?
  4. Fork/Join框架是什么?

Released under the MIT License.