引言

并发编程是Java面试中的重点和难点,涉及线程、锁、内存模型等复杂概念。本文精选了最常见的并发编程面试题,并提供详细的解析和代码示例。

一、线程基础

1.1 创建线程的几种方式

方式1:继承Thread类

1
2
3
4
5
6
7
8
9
10
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread running: " + getName());
}
}

// 使用
MyThread thread = new MyThread();
thread.start();

方式2:实现Runnable接口

1
2
3
4
5
6
7
8
9
10
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable running");
}
}

// 使用
Thread thread = new Thread(new MyRunnable());
thread.start();

方式3:实现Callable接口

1
2
3
4
5
6
7
8
9
10
11
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable result";
}
}

// 使用
FutureTask<String> future = new FutureTask<>(new MyCallable());
new Thread(future).start();
String result = future.get(); // 获取返回值

方式4:使用线程池

1
2
3
4
ExecutorService executor = Executors.newFixedThreadPool(10);
executor.submit(() -> {
System.out.println("Thread pool task");
});

对比总结:

方式 优点 缺点 适用场景
继承Thread 简单直观 不能继承其他类 简单任务
实现Runnable 可以继承其他类 无返回值 一般任务
实现Callable 有返回值,可抛异常 稍复杂 需要返回值的任务
线程池 复用线程,管理方便 需要额外配置 生产环境

1.2 线程的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
     start()
NEW ---------> RUNNABLE <-------> RUNNING
| |
| | wait()/sleep()/join()
| v
| BLOCKED / WAITING
| |
|<-----------------+
|
| 执行完成或异常
v
TERMINATED

状态说明:

1
2
3
4
5
6
7
8
public enum State {
NEW, // 新建
RUNNABLE, // 可运行
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 超时等待
TERMINATED // 终止
}

状态转换示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Thread thread = new Thread(() -> {
synchronized(lock) {
try {
lock.wait(1000); // TIMED_WAITING
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});

// NEW
System.out.println(thread.getState()); // NEW

thread.start();
// RUNNABLE
System.out.println(thread.getState()); // RUNNABLE

Thread.sleep(100);
// TIMED_WAITING
System.out.println(thread.getState()); // TIMED_WAITING

thread.join();
// TERMINATED
System.out.println(thread.getState()); // TERMINATED

二、synchronized关键字

2.1 synchronized的三种使用方式

1. 修饰实例方法

1
2
3
4
5
6
7
8
9
10
public synchronized void method() {
// 锁对象是this
}

// 等价于
public void method() {
synchronized(this) {
// ...
}
}

2. 修饰静态方法

1
2
3
4
5
6
7
8
9
10
public static synchronized void staticMethod() {
// 锁对象是Class对象
}

// 等价于
public static void staticMethod() {
synchronized(MyClass.class) {
// ...
}
}

3. 修饰代码块

1
2
3
4
5
public void method() {
synchronized(lock) {
// 锁对象是lock
}
}

2.2 synchronized的底层原理

字节码层面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void syncMethod() {
synchronized(this) {
count++;
}
}

// 编译后的字节码
public void syncMethod();
Code:
0: aload_0
1: dup
2: astore_1
3: monitorenter // 进入同步块
4: aload_0
5: dup
6: getfield #2 // Field count:I
9: iconst_1
10: iadd
11: putfield #2 // Field count:I
14: aload_1
15: monitorexit // 退出同步块

实现原理:

  • 基于Monitor对象
  • 使用monitorenter和monitorexit指令
  • 每个对象都有一个Monitor
  • 线程获取Monitor才能进入同步代码块

2.3 synchronized的锁升级机制

锁的四种状态:

1
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

1. 偏向锁

1
2
// 第一个线程访问,CAS记录线程ID
// 后续该线程再次访问,直接通过,无需CAS

2. 轻量级锁

1
2
// 有竞争但竞争不激烈时
// 使用CAS自旋等待

3. 重量级锁

1
2
// 竞争激烈时
// 阻塞等待,涉及内核态切换

三、volatile关键字

3.1 volatile的作用

作用1:保证可见性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class VisibilityDemo {
private volatile boolean flag = false;

// 线程1
public void writer() {
flag = true; // 写入立即刷新到主内存
}

// 线程2
public void reader() {
while (!flag) { // 每次从主内存读取
// 如果没有volatile,可能一直循环
}
System.out.println("Flag is true now!");
}
}

作用2:禁止指令重排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Singleton {
private volatile static Singleton instance;

public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
// volatile防止指令重排序
// 1. 分配内存
// 2. 初始化对象
// 3. instance指向内存地址
instance = new Singleton();
}
}
}
return instance;
}
}

3.2 volatile不能保证原子性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class VolatileAtomicityDemo {
private volatile int count = 0;

public void increment() {
count++; // 非原子操作!
// 1. 读取count
// 2. count + 1
// 3. 写回count
}
}

// 解决方案:使用AtomicInteger
class AtomicDemo {
private AtomicInteger count = new AtomicInteger(0);

public void increment() {
count.incrementAndGet(); // 原子操作
}
}

3.3 synchronized vs volatile

特性 synchronized volatile
原子性 ✓ ✗
可见性 ✓ ✓
有序性 ✓ ✓
适用场景 多个操作需要原子性 单个变量读写
性能 较重 较轻

四、Lock接口

4.1 ReentrantLock vs synchronized

ReentrantLock示例:

1
2
3
4
5
6
7
8
9
10
11
12
class LockDemo {
private final ReentrantLock lock = new ReentrantLock();

public void method() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock(); // 必须在finally中释放锁
}
}
}

功能对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 可中断锁
lock.lockInterruptibly(); // 可以响应中断

// 2. 尝试获取锁
if (lock.tryLock()) {
try {
// 获取到锁
} finally {
lock.unlock();
}
} else {
// 未获取到锁
}

// 3. 超时获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) {
// 1秒内获取到锁
}

// 4. 公平锁
ReentrantLock fairLock = new ReentrantLock(true);

// 5. 多个条件变量
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();

4.2 ReadWriteLock读写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class ReadWriteLockDemo {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();

private String data = "";

// 读操作
public String read() {
readLock.lock();
try {
return data;
} finally {
readLock.unlock();
}
}

// 写操作
public void write(String newData) {
writeLock.lock();
try {
data = newData;
} finally {
writeLock.unlock();
}
}
}

读写锁规则:

  • 读-读:不互斥
  • 读-写:互斥
  • 写-写:互斥

五、线程池

5.1 线程池的核心参数

1
2
3
4
5
6
7
8
9
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)

执行流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
提交任务
|
v
核心线程数 < corePoolSize?
|--是--> 创建核心线程执行
|
v--否
|
任务队列是否已满?
|--否--> 加入队列等待
|
v--是
|
线程数 < maximumPoolSize?
|--是--> 创建非核心线程执行
|
v--否
|
执行拒绝策略

5.2 常见线程池类型

1
2
3
4
5
6
7
8
9
10
11
// 1. FixedThreadPool - 固定大小
ExecutorService fixed = Executors.newFixedThreadPool(10);

// 2. CachedThreadPool - 缓存线程池
ExecutorService cached = Executors.newCachedThreadPool();

// 3. SingleThreadExecutor - 单线程
ExecutorService single = Executors.newSingleThreadExecutor();

// 4. ScheduledThreadPool - 定时任务
ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(5);

不推荐使用Executors创建线程池的原因:

1
2
3
4
5
6
// FixedThreadPool和SingleThreadExecutor
// 使用无界队列LinkedBlockingQueue,可能OOM
new LinkedBlockingQueue<Runnable>() // Integer.MAX_VALUE

// CachedThreadPool
// 最大线程数为Integer.MAX_VALUE,可能创建大量线程

推荐手动创建:

1
2
3
4
5
6
7
8
ThreadPoolExecutor executor = new ThreadPoolExecutor(
10, // 核心线程数
20, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);

5.3 四种拒绝策略

1
2
3
4
5
6
7
8
9
10
11
// 1. AbortPolicy - 抛出异常(默认)
new ThreadPoolExecutor.AbortPolicy()

// 2. CallerRunsPolicy - 调用者线程执行
new ThreadPoolExecutor.CallerRunsPolicy()

// 3. DiscardPolicy - 直接丢弃
new ThreadPoolExecutor.DiscardPolicy()

// 4. DiscardOldestPolicy - 丢弃最老的任务
new ThreadPoolExecutor.DiscardOldestPolicy()

六、并发容器

6.1 ConcurrentHashMap

JDK 1.8实现原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1. 数组+链表/红黑树
// 2. CAS + synchronized
// 3. 锁粒度:Node节点

final V putVal(K key, V value, boolean onlyIfAbsent) {
for (Node<K,V>[] tab = table;;) {
// 桶为空,CAS插入
if ((f = tabAt(tab, i)) == null) {
if (casTabAt(tab, i, null, node))
break;
}
// 桶不为空,synchronized锁住桶
else {
synchronized (f) {
// 插入节点
}
}
}
}

6.2 CopyOnWriteArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 复制数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

特点:

  • 写时复制,读不加锁
  • 适合读多写少场景
  • 内存占用较大

6.3 BlockingQueue

常见实现:

1
2
3
4
5
6
7
8
9
10
11
// 1. ArrayBlockingQueue - 有界队列
BlockingQueue<String> queue1 = new ArrayBlockingQueue<>(100);

// 2. LinkedBlockingQueue - 链表队列
BlockingQueue<String> queue2 = new LinkedBlockingQueue<>();

// 3. PriorityBlockingQueue - 优先级队列
BlockingQueue<String> queue3 = new PriorityBlockingQueue<>();

// 4. SynchronousQueue - 同步队列
BlockingQueue<String> queue4 = new SynchronousQueue<>();

核心方法:

1
2
3
4
5
6
7
8
9
10
11
// 1. put/take - 阻塞
queue.put("item"); // 队列满时阻塞
String item = queue.take(); // 队列空时阻塞

// 2. offer/poll - 超时
queue.offer("item", 1, TimeUnit.SECONDS);
String item = queue.poll(1, TimeUnit.SECONDS);

// 3. add/remove - 抛异常
queue.add("item"); // 队列满时抛异常
String item = queue.remove(); // 队列空时抛异常

七、AQS原理

7.1 AQS核心概念

1
2
3
4
5
6
7
8
public abstract class AbstractQueuedSynchronizer {
// 同步状态
private volatile int state;

// 等待队列(FIFO双向队列)
private transient volatile Node head;
private transient volatile Node tail;
}

基于AQS的同步器:

  • ReentrantLock
  • Semaphore
  • CountDownLatch
  • CyclicBarrier
  • ReentrantReadWriteLock

7.2 AQS的两种模式

1. 独占模式(Exclusive)

1
2
3
4
5
6
7
8
// ReentrantLock使用独占模式
public void lock() {
sync.acquire(1); // 获取锁
}

public void unlock() {
sync.release(1); // 释放锁
}

2. 共享模式(Shared)

1
2
3
4
5
6
7
8
// Semaphore使用共享模式
public void acquire() {
sync.acquireShared(1); // 获取许可
}

public void release() {
sync.releaseShared(1); // 释放许可
}

八、常见并发工具类

8.1 CountDownLatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 等待多个线程完成
CountDownLatch latch = new CountDownLatch(3);

// 工作线程
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Task done");
latch.countDown(); // 计数减1
}).start();
}

// 主线程等待
latch.await(); // 等待计数为0
System.out.println("All tasks completed");

8.2 CyclicBarrier

1
2
3
4
5
6
7
8
9
10
11
12
// 等待所有线程到达屏障点
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads arrived");
});

for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("Thread " + Thread.currentThread().getName() + " arrived");
barrier.await(); // 等待其他线程
System.out.println("Thread " + Thread.currentThread().getName() + " continued");
}).start();
}

8.3 Semaphore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 限制并发访问数量
Semaphore semaphore = new Semaphore(3); // 3个许可

for (int i = 0; i < 10; i++) {
new Thread(() -> {
semaphore.acquire(); // 获取许可
try {
System.out.println("Working...");
Thread.sleep(1000);
} finally {
semaphore.release(); // 释放许可
}
}).start();
}

总结

Java并发编程的核心知识点:

  1. 线程基础:创建方式、生命周期
  2. synchronized:使用方式、底层原理、锁升级
  3. volatile:可见性、有序性、不保证原子性
  4. Lock:ReentrantLock、ReadWriteLock
  5. 线程池:核心参数、执行流程、拒绝策略
  6. 并发容器:ConcurrentHashMap、CopyOnWriteArrayList
  7. AQS:独占模式、共享模式
  8. 工具类:CountDownLatch、CyclicBarrier、Semaphore

参考资料