Jacky's blog
首页
  • 学习笔记

    • web
    • android
    • iOS
    • vue
  • 分类
  • 标签
  • 归档
收藏
  • tool
  • algo
  • python
  • java
  • server
  • growth
  • frida
  • blog
  • SP
  • more
GitHub (opens new window)

Jack Yang

编程; 随笔
首页
  • 学习笔记

    • web
    • android
    • iOS
    • vue
  • 分类
  • 标签
  • 归档
收藏
  • tool
  • algo
  • python
  • java
  • server
  • growth
  • frida
  • blog
  • SP
  • more
GitHub (opens new window)
  • shell

  • tool

  • 网络

  • algo

  • compute_base

  • blog

  • growth

  • java

    • java base
    • Java 面试高频问题指南
      • 📚 学习路径
        • 面试准备建议
      • 1️⃣ Java 多线程与并发编程
        • 1.1 线程顺序执行问题
        • 解法一:使用 join() 方法
        • 解法二:使用 CountDownLatch
        • 解法三:使用 CompletableFuture(推荐)
        • 1.2 Lock vs synchronized
        • synchronized vs Lock 对比
        • Lock 的核心优势
        • 实战:实现高效读写缓存
        • 锁升级:从 synchronized 到 ReadWriteLock
        • 1.3 wait() vs sleep()
        • 核心区别对比
        • 代码示例对比
        • sleep() 示例
        • 面试加分项
        • 1.4 实现阻塞队列
        • 方案一:使用 wait/notify 实现
        • 方案二:使用 Lock 和 Condition
        • 方案三:使用 JDK 自带的 BlockingQueue
        • 1.5 生产者-消费者模式
        • 完整实现:生产者-消费者
        • 拓展:哲学家进餐问题
        • 1.6 死锁问题
        • 死锁的四个必要条件
        • 死锁示例代码
        • 解决方案
        • 1.7 volatile 关键字
        • volatile 的作用
        • volatile vs synchronized
        • 代码示例
        • volatile 不能保证原子性
        • 1.8 其他高频问题速查
        • start() vs run()
        • CountDownLatch vs CyclicBarrier
        • 不可变对象
      • 2️⃣ 回调 vs 订阅模式
        • 核心对比
        • 1. 解耦合性(Decoupling)
        • 回调函数
        • 订阅机制
        • 2. 管理多个订阅者
        • 回调函数:手动管理列表
        • 订阅机制:原生支持
        • 3. 自动清理和管理
        • 回调函数:容易内存泄漏
        • 订阅机制:自动管理
        • 4. 可组合性(Composability)
        • 回调函数:难以组合
        • 订阅机制:流式组合
        • 5. 异步流处理
        • 回调函数:回调地狱
        • 订阅机制:优雅处理
        • 6. 响应式编程
        • 回调函数:不支持响应式
        • 订阅机制:自动响应
        • 7. 实战对比:状态管理
        • 回调方式(传统)
        • 订阅方式(现代)
        • 总结
      • 📖 参考资源
        • 官方文档
        • 推荐书籍
        • 在线资源
      • 🎯 面试建议
        • 回答技巧
        • 常见追问
        • 面试准备清单
    • other

    • throwable
    • thread
    • jvm
    • weakreference
    • UnSafe
    • collections
    • Class
    • classloader
  • C&C++

  • ai

  • secure

  • cms

  • english

  • 生活

  • 金融学

  • more

  • other
  • java
Jacky
2024-06-05
目录

Java 面试高频问题指南

# 📚 学习路径

# 面试准备建议

阶段 重点内容 准备时长
基础阶段 Java 基础语法、集合、异常处理 1-2 周
进阶阶段 多线程、JVM、设计模式 2-3 周
高级阶段 并发编程、性能优化、分布式 3-4 周
实战阶段 项目经验总结、场景题 1-2 周

# 1️⃣ Java 多线程与并发编程

参考资料:https://www.cnblogs.com/java1024/p/13390538.html (opens new window) | 相关笔记

# 1.1 线程顺序执行问题

面试题:现在有 T1、T2、T3 三个线程,你怎样保证 T2 在 T1 执行完后执行,T3 在 T2 执行完后执行?

考察点:线程协调机制、join() 方法的理解

# 解法一:使用 join() 方法

public class ThreadOrderDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("T1 执行中...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T1 执行完毕");
        }, "T1");

        Thread t2 = new Thread(() -> {
            System.out.println("T2 执行中...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T2 执行完毕");
        }, "T2");

        Thread t3 = new Thread(() -> {
            System.out.println("T3 执行中...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("T3 执行完毕");
        }, "T3");

        // 启动 T1
        t1.start();
        // 等待 T1 完成
        t1.join();

        // 启动 T2
        t2.start();
        // 等待 T2 完成
        t2.join();

        // 启动 T3
        t3.start();
        // 等待 T3 完成
        t3.join();

        System.out.println("所有线程执行完毕");
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

输出:

T1 执行中...
T1 执行完毕
T2 执行中...
T2 执行完毕
T3 执行中...
T3 执行完毕
所有线程执行完毕
1
2
3
4
5
6
7

# 解法二:使用 CountDownLatch

import java.util.concurrent.CountDownLatch;

public class ThreadOrderWithLatch {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch1 = new CountDownLatch(1);
        CountDownLatch latch2 = new CountDownLatch(1);

        Thread t1 = new Thread(() -> {
            System.out.println("T1 执行");
            latch1.countDown(); // T1 完成,释放信号
        });

        Thread t2 = new Thread(() -> {
            try {
                latch1.await(); // 等待 T1 完成
                System.out.println("T2 执行");
                latch2.countDown(); // T2 完成,释放信号
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t3 = new Thread(() -> {
            try {
                latch2.await(); // 等待 T2 完成
                System.out.println("T3 执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40

# 解法三:使用 CompletableFuture(推荐)

import java.util.concurrent.CompletableFuture;

public class ThreadOrderWithFuture {
    public static void main(String[] args) {
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            System.out.println("T1 执行");
        });

        CompletableFuture<Void> future2 = future1.thenRun(() -> {
            System.out.println("T2 执行");
        });

        CompletableFuture<Void> future3 = future2.thenRun(() -> {
            System.out.println("T3 执行");
        });

        // 等待所有任务完成
        future3.join();
        System.out.println("所有任务完成");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

方案对比:

方案 优点 缺点 适用场景
join() 简单直观,JDK 自带 阻塞主线程,灵活性差 简单的顺序执行
CountDownLatch 灵活,支持多线程协调 代码稍复杂,需要手动管理 多线程等待场景
CompletableFuture 异步非阻塞,支持链式调用 JDK 8+ 现代异步编程

# 1.2 Lock vs synchronized

面试题:在 Java 中 Lock 接口比 synchronized 块的优势是什么?如何实现一个高效的读写缓存?

考察点:锁机制的深入理解、读写锁的应用

# synchronized vs Lock 对比

特性 synchronized Lock
使用方式 关键字,自动加锁/释放 接口,手动 lock()/unlock()
锁类型 可重入、非公平 可重入、可公平、可中断
性能 JDK 6+ 优化后相当 高并发下略优
灵活性 低(无法中断、超时) 高(tryLock、lockInterruptibly)
读写分离 ❌ 不支持 ✅ ReadWriteLock
条件变量 只有 wait/notify 支持多个 Condition

# Lock 的核心优势

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class LockAdvantages {
    private final Lock lock = new ReentrantLock();

    // 1. 可中断锁
    public void interruptibleLock() throws InterruptedException {
        lock.lockInterruptibly(); // 可以被 interrupt() 打断
        try {
            // 业务逻辑
        } finally {
            lock.unlock();
        }
    }

    // 2. 尝试获取锁(非阻塞)
    public boolean tryLock() {
        if (lock.tryLock()) {
            try {
                // 获取到锁,执行业务
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false; // 未获取到锁,直接返回
    }

    // 3. 超时获取锁
    public boolean tryLockWithTimeout() throws InterruptedException {
        if (lock.tryLock(3, TimeUnit.SECONDS)) {
            try {
                // 3 秒内获取到锁
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false; // 超时未获取到锁
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 实战:实现高效读写缓存

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 高性能读写缓存
 * - 多个线程可以同时读
 * - 只有一个线程可以写
 * - 写线程会阻塞所有读线程
 */
public class ReadWriteCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

    // 读操作(共享锁)
    public V get(K key) {
        rwLock.readLock().lock(); // 多个线程可以同时持有读锁
        try {
            System.out.println(Thread.currentThread().getName() + " 读取数据");
            return cache.get(key);
        } finally {
            rwLock.readLock().unlock();
        }
    }

    // 写操作(排他锁)
    public void put(K key, V value) {
        rwLock.writeLock().lock(); // 只有一个线程可以持有写锁
        try {
            System.out.println(Thread.currentThread().getName() + " 写入数据");
            Thread.sleep(100); // 模拟写入耗时
            cache.put(key, value);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            rwLock.writeLock().unlock();
        }
    }

    // 测试读写性能
    public static void main(String[] args) {
        ReadWriteCache<String, String> cache = new ReadWriteCache<>();

        // 1 个写线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                cache.put("key" + i, "value" + i);
            }
        }, "Writer").start();

        // 10 个读线程(可以并发执行)
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    cache.get("key" + j);
                }
            }, "Reader-" + i).start();
        }
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

输出示例:

Reader-0 读取数据
Reader-1 读取数据  // 多个读线程并发执行
Reader-2 读取数据
Writer 写入数据    // 写线程阻塞所有读线程
Reader-3 读取数据
...
1
2
3
4
5
6

# 锁升级:从 synchronized 到 ReadWriteLock

// ❌ 低效方案:所有操作都互斥
public class SynchronizedCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();

    public synchronized V get(K key) {
        return cache.get(key); // 读操作也需要等待
    }

    public synchronized void put(K key, V value) {
        cache.put(key, value);
    }
}

// ✅ 高效方案:读写分离
public class RWLockCache<K, V> {
    private final Map<K, V> cache = new HashMap<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    public V get(K key) {
        lock.readLock().lock();
        try {
            return cache.get(key); // 多个线程可以同时读
        } finally {
            lock.readLock().unlock();
        }
    }

    public void put(K key, V value) {
        lock.writeLock().lock();
        try {
            cache.put(key, value); // 写操作独占
        } finally {
            lock.writeLock().unlock();
        }
    }
}
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
28
29
30
31
32
33
34
35
36

# 1.3 wait() vs sleep()

面试题:在 Java 中 wait 和 sleep 方法的不同?

考察点:线程状态管理、锁机制

# 核心区别对比

维度 wait() sleep()
所属类 Object 类的方法 Thread 类的静态方法
锁释放 ✅ 释放锁(其他线程可获取) ❌ 不释放锁
使用场景 线程间通信(配合 notify) 暂停当前线程
调用位置 必须在 synchronized 块内 任何地方都可调用
唤醒方式 notify()/notifyAll() 时间到自动唤醒
异常 InterruptedException InterruptedException

# 代码示例对比

public class WaitVsSleep {
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        // 示例 1:wait() 会释放锁
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("T1: 持有锁");
                try {
                    System.out.println("T1: 调用 wait(),释放锁");
                    lock.wait(); // 释放锁,其他线程可以获取
                    System.out.println("T1: 被唤醒,重新获取锁");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("T2: 获取到锁(因为 T1 释放了)");
                lock.notify(); // 唤醒 T1
                System.out.println("T2: 唤醒 T1,但仍持有锁");
                try {
                    Thread.sleep(2000); // T2 sleep 不释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("T2: 释放锁");
            } // T2 退出 synchronized 块,释放锁
        });

        t1.start();
        Thread.sleep(100); // 确保 T1 先执行
        t2.start();

        t1.join();
        t2.join();
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40

输出:

T1: 持有锁
T1: 调用 wait(),释放锁
T2: 获取到锁(因为 T1 释放了)
T2: 唤醒 T1,但仍持有锁
T2: 释放锁
T1: 被唤醒,重新获取锁
1
2
3
4
5
6

# sleep() 示例

public class SleepDemo {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("T1: 持有锁");
                try {
                    System.out.println("T1: 调用 sleep(2000)");
                    Thread.sleep(2000); // 不释放锁!
                    System.out.println("T1: sleep 结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("T2: 获取到锁");
            }
        });

        t1.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start(); // T2 需要等待 T1 的 sleep 结束
    }
}
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
28
29
30
31
32

输出:

T1: 持有锁
T1: 调用 sleep(2000)
// 等待 2 秒...
T1: sleep 结束
T2: 获取到锁  // T2 必须等 T1 释放锁
1
2
3
4
5

# 面试加分项

典型使用场景:

// wait/notify 场景:生产者-消费者
class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int MAX_SIZE = 10;

    public synchronized void produce(int item) throws InterruptedException {
        while (queue.size() == MAX_SIZE) {
            wait(); // 队列满,等待消费
        }
        queue.add(item);
        notifyAll(); // 通知消费者
    }

    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // 队列空,等待生产
        }
        int item = queue.poll();
        notifyAll(); // 通知生产者
        return item;
    }
}

// sleep 场景:定时任务
class ScheduledTask {
    public void execute() {
        while (true) {
            try {
                performTask();
                Thread.sleep(5000); // 每 5 秒执行一次
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }

    private void performTask() {
        System.out.println("执行定时任务");
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41

记忆口诀

  • wait:我在等(wait)别人通知(notify),所以要释放锁让别人进来通知我
  • sleep:我只是睡(sleep)一会儿,锁还在我手上,别人别想拿

# 1.4 实现阻塞队列

面试题:用 Java 实现一个阻塞队列

考察点:并发编程能力、wait/notify 机制、JUC 并发工具

# 方案一:使用 wait/notify 实现

import java.util.LinkedList;
import java.util.Queue;

/**
 * 手写阻塞队列
 * - put:队列满时阻塞
 * - take:队列空时阻塞
 */
public class MyBlockingQueue<E> {
    private final Queue<E> queue = new LinkedList<>();
    private final int capacity;

    public MyBlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    // 放入元素(队列满时阻塞)
    public synchronized void put(E element) throws InterruptedException {
        // 注意:必须用 while 而不是 if(防止虚假唤醒)
        while (queue.size() == capacity) {
            System.out.println(Thread.currentThread().getName() + " 队列已满,等待...");
            wait(); // 释放锁,等待消费
        }
        
        queue.add(element);
        System.out.println(Thread.currentThread().getName() + " 放入元素:" + element);
        
        notifyAll(); // 唤醒所有等待的消费者
    }

    // 取出元素(队列空时阻塞)
    public synchronized E take() throws InterruptedException {
        while (queue.isEmpty()) {
            System.out.println(Thread.currentThread().getName() + " 队列为空,等待...");
            wait(); // 释放锁,等待生产
        }
        
        E element = queue.poll();
        System.out.println(Thread.currentThread().getName() + " 取出元素:" + element);
        
        notifyAll(); // 唤醒所有等待的生产者
        return element;
    }

    public synchronized int size() {
        return queue.size();
    }

    // 测试
    public static void main(String[] args) {
        MyBlockingQueue<Integer> queue = new MyBlockingQueue<>(3);

        // 生产者
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    queue.put(i);
                    Thread.sleep(100);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Producer").start();

        // 消费者
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    queue.take();
                    Thread.sleep(300); // 消费慢于生产
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "Consumer").start();
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# 方案二:使用 Lock 和 Condition

import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 使用 Lock 和 Condition 实现阻塞队列
 * - 性能优于 synchronized
 * - 可以分别唤醒生产者和消费者
 */
public class LockBlockingQueue<E> {
    private final Queue<E> queue = new LinkedList<>();
    private final int capacity;
    private final Lock lock = new ReentrantLock();
    
    // 两个条件变量:分别控制生产者和消费者
    private final Condition notFull = lock.newCondition();  // 队列未满
    private final Condition notEmpty = lock.newCondition(); // 队列非空

    public LockBlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(E element) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                System.out.println(Thread.currentThread().getName() + " 队列已满,等待...");
                notFull.await(); // 等待队列未满
            }
            
            queue.add(element);
            System.out.println(Thread.currentThread().getName() + " 放入:" + element);
            
            notEmpty.signal(); // 唤醒一个消费者
        } finally {
            lock.unlock();
        }
    }

    public E take() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                System.out.println(Thread.currentThread().getName() + " 队列为空,等待...");
                notEmpty.await(); // 等待队列非空
            }
            
            E element = queue.poll();
            System.out.println(Thread.currentThread().getName() + " 取出:" + element);
            
            notFull.signal(); // 唤醒一个生产者
            return element;
        } finally {
            lock.unlock();
        }
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

# 方案三:使用 JDK 自带的 BlockingQueue

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class JDKBlockingQueueDemo {
    public static void main(String[] args) {
        // JDK 提供的阻塞队列
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(3);

        // 生产者
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    queue.put(i); // 队列满时自动阻塞
                    System.out.println("生产:" + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // 消费者
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    int val = queue.take(); // 队列空时自动阻塞
                    System.out.println("消费:" + val);
                    Thread.sleep(300);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
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
28
29
30
31
32
33
34

为什么用 while 而不是 if?

// ❌ 错误写法
if (queue.size() == capacity) {
    wait(); // 虚假唤醒后不会再检查条件
}

// ✅ 正确写法
while (queue.size() == capacity) {
    wait(); // 唤醒后会重新检查条件
}
1
2
3
4
5
6
7
8
9

虚假唤醒:线程可能在没有被 notify 的情况下自动唤醒(底层操作系统原因),使用 while 可以确保条件仍然满足。

# 1.5 生产者-消费者模式

面试题:用 Java 写代码来解决生产者-消费者问题

考察点:经典并发模式、线程协作

# 完整实现:生产者-消费者

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 生产者-消费者模式
 * - 多个生产者
 * - 多个消费者
 * - 共享缓冲区
 */
public class ProducerConsumerDemo {
    
    // 生产者
    static class Producer implements Runnable {
        private final BlockingQueue<Integer> queue;
        private final int id;

        public Producer(BlockingQueue<Integer> queue, int id) {
            this.queue = queue;
            this.id = id;
        }

        @Override
        public void run() {
            try {
                for (int i = 0; i < 5; i++) {
                    int product = id * 100 + i;
                    queue.put(product);
                    System.out.println("生产者-" + id + " 生产:" + product + 
                                     " [队列大小:" + queue.size() + "]");
                    Thread.sleep((int) (Math.random() * 1000));
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    // 消费者
    static class Consumer implements Runnable {
        private final BlockingQueue<Integer> queue;
        private final int id;

        public Consumer(BlockingQueue<Integer> queue, int id) {
            this.queue = queue;
            this.id = id;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    Integer product = queue.take();
                    System.out.println("  消费者-" + id + " 消费:" + product + 
                                     " [队列大小:" + queue.size() + "]");
                    Thread.sleep((int) (Math.random() * 1500));
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(5);

        // 3 个生产者
        for (int i = 1; i <= 3; i++) {
            new Thread(new Producer(queue, i), "Producer-" + i).start();
        }

        // 2 个消费者
        for (int i = 1; i <= 2; i++) {
            new Thread(new Consumer(queue, i), "Consumer-" + i).start();
        }
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

输出示例:

生产者-1 生产:100 [队列大小:1]
生产者-2 生产:200 [队列大小:2]
  消费者-1 消费:100 [队列大小:1]
生产者-3 生产:300 [队列大小:2]
  消费者-2 消费:200 [队列大小:1]
生产者-1 生产:101 [队列大小:2]
...
1
2
3
4
5
6
7

# 拓展:哲学家进餐问题

import java.util.concurrent.Semaphore;

/**
 * 哲学家进餐问题
 * - 5 个哲学家,5 支筷子
 * - 每个哲学家需要 2 支筷子才能进餐
 * - 防止死锁:最多允许 4 个哲学家同时拿筷子
 */
public class DiningPhilosophers {
    
    static class Philosopher extends Thread {
        private final int id;
        private final Semaphore leftChopstick;
        private final Semaphore rightChopstick;
        private final Semaphore maxDiners; // 限制同时进餐人数

        public Philosopher(int id, Semaphore left, Semaphore right, Semaphore maxDiners) {
            this.id = id;
            this.leftChopstick = left;
            this.rightChopstick = right;
            this.maxDiners = maxDiners;
        }

        @Override
        public void run() {
            try {
                for (int i = 0; i < 3; i++) {
                    think();
                    eat();
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }

        private void think() throws InterruptedException {
            System.out.println("哲学家-" + id + " 正在思考...");
            Thread.sleep((int) (Math.random() * 1000));
        }

        private void eat() throws InterruptedException {
            // 限制同时进餐人数,防止死锁
            maxDiners.acquire();
            
            // 拿左边筷子
            leftChopstick.acquire();
            System.out.println("哲学家-" + id + " 拿起左边筷子");
            
            // 拿右边筷子
            rightChopstick.acquire();
            System.out.println("哲学家-" + id + " 拿起右边筷子,开始进餐");
            
            Thread.sleep((int) (Math.random() * 1000));
            
            // 放下筷子
            System.out.println("哲学家-" + id + " 进餐完毕,放下筷子");
            rightChopstick.release();
            leftChopstick.release();
            
            maxDiners.release();
        }
    }

    public static void main(String[] args) {
        int n = 5;
        Semaphore[] chopsticks = new Semaphore[n];
        for (int i = 0; i < n; i++) {
            chopsticks[i] = new Semaphore(1); // 每支筷子只能被一个人拿
        }

        // 最多 4 个哲学家同时进餐(防止死锁)
        Semaphore maxDiners = new Semaphore(n - 1);

        for (int i = 0; i < n; i++) {
            Semaphore left = chopsticks[i];
            Semaphore right = chopsticks[(i + 1) % n];
            new Philosopher(i, left, right, maxDiners).start();
        }
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80

# 1.6 死锁问题

面试题:用 Java 编程一个会导致死锁的程序,你将怎么解决?

考察点:死锁的四个必要条件、死锁预防与避免

# 死锁的四个必要条件

  1. 互斥条件:资源只能被一个线程占用
  2. 请求与保持:线程持有资源的同时请求新资源
  3. 不剥夺条件:资源不能被强制剥夺
  4. 循环等待:多个线程形成环路等待资源

# 死锁示例代码

public class DeadlockDemo {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        // 线程 1:先获取 lock1,再获取 lock2
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("T1: 持有 lock1,等待 lock2...");
                try {
                    Thread.sleep(100); // 增加死锁概率
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("T1: 获取 lock2");
                }
            }
        }, "T1");

        // 线程 2:先获取 lock2,再获取 lock1(顺序相反)
        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("T2: 持有 lock2,等待 lock1...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("T2: 获取 lock1");
                }
            }
        }, "T2");

        t1.start();
        t2.start();
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39

输出(死锁):

T1: 持有 lock1,等待 lock2...
T2: 持有 lock2,等待 lock1...
// 程序卡住,T1 等 lock2,T2 等 lock1,形成循环等待
1
2
3

# 解决方案

方案一:统一加锁顺序

public class DeadlockFree1 {
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        // ✅ 两个线程都按照相同的顺序获取锁
        Thread t1 = new Thread(() -> {
            synchronized (lock1) {  // 先 lock1
                System.out.println("T1: 持有 lock1");
                synchronized (lock2) {  // 再 lock2
                    System.out.println("T1: 获取 lock2");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (lock1) {  // 也是先 lock1
                System.out.println("T2: 持有 lock1");
                synchronized (lock2) {  // 再 lock2
                    System.out.println("T2: 获取 lock2");
                }
            }
        });

        t1.start();
        t2.start();
    }
}
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
28

方案二:使用 tryLock 超时机制

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class DeadlockFree2 {
    private static final Lock lock1 = new ReentrantLock();
    private static final Lock lock2 = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                // 尝试获取锁,超时则放弃
                if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
                    try {
                        System.out.println("T1: 获取 lock1");
                        Thread.sleep(50);
                        
                        if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
                            try {
                                System.out.println("T1: 获取 lock2");
                            } finally {
                                lock2.unlock();
                            }
                        } else {
                            System.out.println("T1: 获取 lock2 超时,放弃");
                        }
                    } finally {
                        lock1.unlock();
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // T2 同理...
        t1.start();
    }
}
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
28
29
30
31
32
33
34
35
36
37
38
39

# 1.7 volatile 关键字

面试题:Java 中的 volatile 关键字有什么作用?它跟 synchronized 有什么不同?

考察点:Java 内存模型、可见性、有序性、原子性

# volatile 的作用

  1. 保证可见性:一个线程修改后,其他线程立即看到
  2. 禁止指令重排序:保证有序性
  3. ❌ 不保证原子性:i++ 等操作仍然不是线程安全的

# volatile vs synchronized

特性 volatile synchronized
可见性 ✅ 保证 ✅ 保证
有序性 ✅ 保证(禁止重排序) ✅ 保证
原子性 ❌ 不保证 ✅ 保证
适用场景 单个变量读写 复合操作
性能 轻量级(无锁) 重量级(加锁)
阻塞 不会阻塞 可能阻塞

# 代码示例

public class VolatileDemo {
    // ❌ 没有 volatile:可能看不到修改
    private static boolean flag = false;

    // ✅ 使用 volatile:立即可见
    private static volatile boolean volatileFlag = false;

    public static void main(String[] args) throws InterruptedException {
        // 测试 1:没有 volatile(可能死循环)
        new Thread(() -> {
            while (!flag) {
                // 可能一直看不到 flag 的修改
            }
            System.out.println("线程 1:flag 变为 true");
        }).start();

        Thread.sleep(100);
        flag = true; // 主线程修改
        System.out.println("主线程:flag 设为 true");

        // 测试 2:使用 volatile(正常退出)
        new Thread(() -> {
            while (!volatileFlag) {
                // volatile 保证可见性
            }
            System.out.println("线程 2:volatileFlag 变为 true");
        }).start();

        Thread.sleep(100);
        volatileFlag = true;
        System.out.println("主线程:volatileFlag 设为 true");
    }
}
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
28
29
30
31
32
33

# volatile 不能保证原子性

public class VolatileNotAtomic {
    private static volatile int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    count++; // ❌ 非原子操作,volatile 无法保证
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("count = " + count); // 结果可能小于 10000
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

正确方案:使用 AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo {
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    count.incrementAndGet(); // ✅ 原子操作
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("count = " + count.get()); // 结果一定是 10000
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 1.8 其他高频问题速查

# start() vs run()

方法 效果 说明
start() ✅ 创建新线程 调用 native 方法启动线程
run() ❌ 不创建线程 只是普通方法调用
Thread t = new Thread(() -> System.out.println("执行中"));

t.start(); // ✅ 在新线程中执行
t.run();   // ❌ 在当前线程中执行(相当于普通方法调用)
1
2
3
4

# CountDownLatch vs CyclicBarrier

特性 CountDownLatch CyclicBarrier
重用性 ❌ 一次性 ✅ 可重用
等待方式 await() 等待计数归零 await() 等待所有线程到达
典型场景 主线程等待子线程完成 多线程互相等待
// CountDownLatch:主线程等待 N 个子线程完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        System.out.println("任务完成");
        latch.countDown(); // 计数 -1
    }).start();
}
latch.await(); // 主线程等待计数归零
System.out.println("所有任务完成");

// CyclicBarrier:N 个线程互相等待
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到达,开始下一阶段");
});
for (int i = 0; i < 3; i++) {
    new Thread(() -> {
        try {
            System.out.println("阶段 1 完成");
            barrier.await(); // 等待其他线程
            System.out.println("阶段 2 开始");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }).start();
}
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

# 不可变对象

定义:创建后状态不能改变的对象

优势:

  1. ✅ 线程安全(无需同步)
  2. ✅ 可以安全共享
  3. ✅ 适合作为 HashMap 的 key
// String 是不可变的
public final class String {
    private final char[] value; // final 修饰
    
    // 没有提供修改方法,所有"修改"都返回新对象
    public String toUpperCase() {
        return new String(upperCaseChars);
    }
}

// 自定义不可变类
public final class ImmutablePerson {
    private final String name;
    private final int age;
    
    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
    
    // ❌ 不提供 setter 方法
}
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

# 2️⃣ 回调 vs 订阅模式

# 核心对比

相比于传统的回调函数(Callback),订阅机制(Subscription)在状态管理和事件处理方面具有以下关键优势:

维度 回调函数 订阅机制
耦合性 紧耦合 松散耦合
多订阅者 需手动管理列表 天生支持
生命周期管理 手动注册/移除 自动清理
可组合性 线性逻辑,难组合 支持复杂组合
异步流处理 容易陷入"回调地狱" 流式处理,优雅
响应式编程 不支持 完全支持

# 1. 解耦合性(Decoupling)

# 回调函数

// ❌ 回调函数:紧耦合
public class UserService {
    private Callback callback;
    
    public void setCallback(Callback callback) {
        this.callback = callback; // 紧密绑定
    }
    
    public void updateUser(User user) {
        // 业务逻辑
        if (callback != null) {
            callback.onUserUpdated(user); // 直接调用
        }
    }
}

// 使用方需要显式注入回调
UserService service = new UserService();
service.setCallback(new Callback() {
    @Override
    public void onUserUpdated(User user) {
        System.out.println("用户更新:" + user);
    }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 订阅机制

// ✅ 订阅:松散耦合
public class UserService {
    private final List<Subscriber<User>> subscribers = new ArrayList<>();
    
    public void subscribe(Subscriber<User> subscriber) {
        subscribers.add(subscriber);
    }
    
    public void updateUser(User user) {
        // 业务逻辑
        notifySubscribers(user); // 发布事件
    }
    
    private void notifySubscribers(User user) {
        for (Subscriber<User> subscriber : subscribers) {
            subscriber.onNext(user);
        }
    }
}

// 订阅者可以独立存在
userService.subscribe(user -> System.out.println("订阅者 1:" + user));
userService.subscribe(user -> System.out.println("订阅者 2:" + user));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 2. 管理多个订阅者

# 回调函数:手动管理列表

// ❌ 回调函数需要手动管理多个回调
public class EventEmitter {
    private final List<Callback> callbacks = new ArrayList<>();
    
    public void addCallback(Callback callback) {
        callbacks.add(callback);
    }
    
    public void removeCallback(Callback callback) {
        callbacks.remove(callback); // 需要手动移除
    }
    
    public void emit(String event) {
        for (Callback callback : callbacks) {
            callback.handle(event);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 订阅机制:原生支持

// ✅ 订阅机制原生支持多订阅者
import io.reactivex.rxjava3.core.Observable;

Observable<String> observable = Observable.just("Event 1", "Event 2");

// 多个订阅者自动管理
observable.subscribe(event -> System.out.println("订阅者 A:" + event));
observable.subscribe(event -> System.out.println("订阅者 B:" + event));
observable.subscribe(event -> System.out.println("订阅者 C:" + event));
1
2
3
4
5
6
7
8
9

# 3. 自动清理和管理

# 回调函数:容易内存泄漏

// ❌ 回调函数需要手动清理
public class Activity {
    private DataService service = new DataService();
    
    public void onCreate() {
        service.setCallback(data -> {
            updateUI(data); // Activity 销毁后仍可能被调用
        });
    }
    
    public void onDestroy() {
        // 忘记移除回调 → 内存泄漏!
        // service.setCallback(null);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 订阅机制:自动管理

// ✅ 订阅机制支持自动清理
import io.reactivex.rxjava3.disposables.Disposable;

public class Activity {
    private Disposable disposable;
    
    public void onCreate() {
        disposable = observable.subscribe(data -> {
            updateUI(data);
        });
    }
    
    public void onDestroy() {
        disposable.dispose(); // 自动取消订阅,防止泄漏
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 4. 可组合性(Composability)

# 回调函数:难以组合

// ❌ 回调地狱
getUserById(userId, user -> {
    getOrdersByUser(user, orders -> {
        getOrderDetails(orders.get(0), details -> {
            getPaymentInfo(details, payment -> {
                // 嵌套4层,难以维护
                processPayment(payment);
            });
        });
    });
});
1
2
3
4
5
6
7
8
9
10
11

# 订阅机制:流式组合

// ✅ 响应式链式调用
getUserById(userId)
    .flatMap(user -> getOrdersByUser(user))
    .flatMap(orders -> getOrderDetails(orders.get(0)))
    .flatMap(details -> getPaymentInfo(details))
    .subscribe(
        payment -> processPayment(payment),
        error -> handleError(error),
        () -> System.out.println("完成")
    );
1
2
3
4
5
6
7
8
9
10

# 5. 异步流处理

# 回调函数:回调地狱

// ❌ 多个异步操作嵌套
fetchData1(result1 -> {
    fetchData2(result2 -> {
        fetchData3(result3 -> {
            // 嵌套太深,难以阅读
        });
    });
});
1
2
3
4
5
6
7
8

# 订阅机制:优雅处理

// ✅ 使用 RxJava 处理异步流
Observable.zip(
    fetchData1(),
    fetchData2(),
    fetchData3(),
    (result1, result2, result3) -> {
        return combineResults(result1, result2, result3);
    }
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
    finalResult -> updateUI(finalResult),
    error -> showError(error)
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 6. 响应式编程

# 回调函数:不支持响应式

// ❌ 手动管理状态更新
public class Counter {
    private int count = 0;
    private Callback callback;
    
    public void increment() {
        count++;
        if (callback != null) {
            callback.onCountChanged(count); // 手动通知
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 订阅机制:自动响应

// ✅ 响应式自动更新
import io.reactivex.rxjava3.subjects.BehaviorSubject;

public class Counter {
    private final BehaviorSubject<Integer> countSubject = BehaviorSubject.createDefault(0);
    
    public void increment() {
        int newCount = countSubject.getValue() + 1;
        countSubject.onNext(newCount); // 自动通知所有订阅者
    }
    
    public Observable<Integer> getCount() {
        return countSubject;
    }
}

// 使用
Counter counter = new Counter();
counter.getCount().subscribe(count -> {
    System.out.println("当前计数:" + count); // 自动响应变化
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 7. 实战对比:状态管理

# 回调方式(传统)

public class UserStore {
    private User currentUser;
    private final List<UserChangeCallback> callbacks = new ArrayList<>();
    
    public void addListener(UserChangeCallback callback) {
        callbacks.add(callback);
    }
    
    public void removeListener(UserChangeCallback callback) {
        callbacks.remove(callback);
    }
    
    public void setUser(User user) {
        this.currentUser = user;
        notifyCallbacks();
    }
    
    private void notifyCallbacks() {
        for (UserChangeCallback callback : callbacks) {
            callback.onUserChanged(currentUser);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 订阅方式(现代)

import io.reactivex.rxjava3.subjects.PublishSubject;

public class UserStore {
    private final PublishSubject<User> userSubject = PublishSubject.create();
    
    public void setUser(User user) {
        userSubject.onNext(user); // 一行代码,自动通知所有订阅者
    }
    
    public Observable<User> observeUser() {
        return userSubject;
    }
}

// 使用
userStore.observeUser()
    .filter(user -> user.getAge() > 18) // 支持转换
    .map(User::getName)
    .distinctUntilChanged() // 去重
    .subscribe(name -> System.out.println("成年用户:" + name));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 总结

回调函数适用场景:

  • 简单的单次事件响应
  • 不需要取消操作
  • 订阅者数量固定且少

订阅机制适用场景:

  • 复杂的状态管理
  • 多个订阅者
  • 需要自动清理资源
  • 异步数据流处理
  • 响应式编程

推荐实践

在现代 Java 开发中,推荐使用订阅机制(如 RxJava、Reactor)来处理复杂的异步场景和状态管理,它能显著提高代码的可读性和可维护性。

# 📖 参考资源

# 官方文档

  • Java Concurrency in Practice (opens new window)
  • Java SE Concurrency (opens new window)
  • RxJava Documentation (opens new window)

# 推荐书籍

  1. 《Java 并发编程实战》(Java Concurrency in Practice)
  2. 《深入理解 Java 虚拟机》(周志明)
  3. 《Effective Java》(Joshua Bloch)

# 在线资源

  • LeetCode 并发题库 (opens new window)
  • 掘金 - Java 面试专栏 (opens new window)
  • 美团技术团队博客 (opens new window)

# 🎯 面试建议

# 回答技巧

  1. 先说原理,再举例子

    • 例:wait() 会释放锁,因为它需要让其他线程进来通知(举生产者-消费者例子)
  2. 对比说明,加深印象

    • 例:对比 volatile 和 synchronized 的异同
  3. 结合实战,展示经验

    • 例:在项目中遇到的死锁问题如何排查和解决
  4. 知识延伸,展现深度

    • 例:从 synchronized 引申到 JVM 的锁优化(偏向锁、轻量级锁、重量级锁)

# 常见追问

基础问题 可能追问
wait() vs sleep() 为什么 wait() 必须在 synchronized 块内?
volatile volatile 如何保证可见性?(提示:内存屏障)
死锁 如何排查生产环境的死锁?(提示:jstack)
ThreadLocal ThreadLocal 可能导致什么问题?(提示:内存泄漏)

# 面试准备清单

  • [ ] 掌握 Java 内存模型(JMM)
  • [ ] 熟悉 JUC 并发包(java.util.concurrent)
  • [ ] 能手写生产者-消费者、死锁代码
  • [ ] 理解 CAS、AQS 原理
  • [ ] 了解线程池的核心参数和工作原理
  • [ ] 掌握常见并发容器(ConcurrentHashMap、CopyOnWriteArrayList)
  • [ ] 熟悉分布式锁(Redis、Zookeeper)

祝你面试顺利!🚀

#interview#java#concurrent#jvm
上次更新: 2025/10/11, 18:35:46
java base
String#format

← java base String#format→

最近更新
01
npx 使用指南
10-12
02
cursor
09-28
03
inspect
07-20
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Jacky | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式