以下是幾道針對Java并發編程的面試題,涵蓋基礎知識、高級概念和實際應用場景,適合資深Java工程師的面試評估:
1. 線程池與任務調度
題目:
- 描述Java線程池的核心參數(如
corePoolSize
、maximumPoolSize
、keepAliveTime
等)的作用,并說明如何根據業務場景選擇合適的線程池類型(如newFixedThreadPool
、newCachedThreadPool
)。 - 請解釋以下代碼片段中線程池的潛在問題,并提出改進建議:
ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) {executor.submit(() -> {// 執行耗時操作}); }
參考答案:
-
核心參數作用:
corePoolSize
:核心線程數,線程池中始終存活的線程數量。maximumPoolSize
:最大線程數,線程池允許創建的最大線程數。keepAliveTime
:非核心線程的存活時間,超過核心數的線程在空閑后會被銷毀。workQueue
:任務隊列,用于緩存未執行的任務。threadFactory
:線程工廠,用于創建線程。handler
:拒絕策略,當任務隊列和線程數均滿時的處理方式。
-
線程池類型選擇:
newFixedThreadPool
:固定大小的線程池,適合負載穩定的場景(如Web服務器)。newCachedThreadPool
:可緩存的線程池,適合短時任務(如異步處理)。newScheduledThreadPool
:支持定時任務,適合周期性任務(如定時清理緩存)。
-
代碼問題:
- 使用
newFixedThreadPool(10)
提交1000個任務,可能導致任務隊列堆積(默認使用無界隊列LinkedBlockingQueue
),占用大量內存甚至導致OOM。 - 改進建議:
- 指定有界隊列(如
ArrayBlockingQueue
),并設置合理的拒絕策略(如CallerRunsPolicy
)。 - 根據任務類型調整線程池大小(如CPU密集型任務線程數為
CPU核心數 + 1
,IO密集型任務線程數可更高)。
- 指定有界隊列(如
- 使用
2. 鎖與同步機制
題目:
- 比較
synchronized
關鍵字和ReentrantLock
的優缺點,并說明在哪些場景下更適合使用ReentrantLock
。 - 請分析以下代碼中的線程安全問題,并提供解決方案:
public class Counter {private int count = 0;public void increment() {count++;} }
參考答案:
-
synchronized
vsReentrantLock
:- 相同點:都支持可重入性,能解決線程安全問題。
- 不同點:
- 靈活性:
ReentrantLock
支持嘗試加鎖(tryLock
)、超時加鎖、條件變量(Condition
)等高級功能,而synchronized
只能通過隱式鎖實現。 - 性能:在低競爭場景下,
synchronized
性能接近ReentrantLock
;在高競爭場景下,ReentrantLock
可通過公平鎖策略減少線程饑餓。 - 使用方式:
synchronized
通過語法糖實現(如方法或代碼塊),而ReentrantLock
需要顯式加鎖和釋放(需在finally
中釋放)。
- 靈活性:
-
代碼問題:
count++
操作(即count = count + 1
)不是原子操作,存在線程安全問題(多個線程可能同時讀取并更新count
的舊值)。- 解決方案:
- 使用
synchronized
修飾increment
方法。 - 使用
ReentrantLock
手動加鎖。 - 使用原子類
AtomicInteger
替代普通int
。
- 使用
3. 并發工具類
題目:
- 請說明
CountDownLatch
、CyclicBarrier
和Semaphore
的使用場景及區別,并提供一個實際應用案例。 - 編寫一個使用
CyclicBarrier
的示例代碼,模擬多個線程協作完成任務的場景。
參考答案:
-
工具類對比:
CountDownLatch
:用于等待一組線程完成任務后繼續執行(如主線程等待所有子線程完成)。CyclicBarrier
:用于多個線程相互等待,達到屏障點后再同時繼續執行(如多線程并行計算后匯總結果)。Semaphore
:用于控制同時訪問某個資源的線程數量(如限流)。
-
案例:
CyclicBarrier
示例:模擬3個線程分別計算數組的不同部分,匯總結果后再輸出:public class CyclicBarrierExample {public static void main(String[] args) {CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("All threads have finished their part!"));int[] data = {1, 2, 3, 4, 5, 6};for (int i = 0; i < 3; i++) {new Thread(() -> {int sum = 0;for (int j = 0; j < 2; j++) {sum += data[ThreadLocalRandom.current().nextInt(data.length)];}System.out.println("Thread " + Thread.currentThread().getId() + " computed sum: " + sum);try {barrier.await();} catch (Exception e) {e.printStackTrace();}}).start();}} }
4. 性能優化與死鎖
題目:
- 如何通過JVM參數和工具(如
jstack
、VisualVM
)定位和解決線程死鎖問題? - 請分析以下代碼可能引發死鎖的原因,并提出優化方案:
public class Deadlock {private final Object lock1 = new Object();private final Object lock2 = new Object();public void methodA() {synchronized (lock1) {synchronized (lock2) {// 業務邏輯}}}public void methodB() {synchronized (lock2) {synchronized (lock1) {// 業務邏輯}}} }
參考答案:
-
死鎖定位與解決:
- 使用
jstack <pid>
生成線程堆棧,查找BLOCKED
狀態的線程及鎖依賴關系。 - 使用
VisualVM
的線程分析工具查看線程狀態和鎖競爭情況。 - 解決方案:避免嵌套鎖,或統一鎖順序(如始終先獲取
lock1
再獲取lock2
)。
- 使用
-
代碼問題:
- 死鎖原因:
methodA
和methodB
以相反順序獲取鎖(lock1
→lock2
vslock2
→lock1
),可能導致兩個線程互相等待對方釋放鎖。 - 優化方案:
- 統一鎖順序(如始終先獲取
lock1
再獲取lock2
)。 - 使用
ReentrantLock.tryLock()
嘗試加鎖,超時后回退。
- 統一鎖順序(如始終先獲取
- 死鎖原因:
5. 高級并發模式
題目:
- 請描述生產者-消費者模型的實現方式,并說明如何通過
BlockingQueue
優化該模型。 - 使用
CompletableFuture
編寫一個異步任務鏈,要求:- 并行執行兩個任務(如查詢數據庫和查詢緩存)。
- 合并結果并返回最終數據。
參考答案:
-
生產者-消費者模型:
- 使用
BlockingQueue
(如ArrayBlockingQueue
)實現線程間通信,生產者將任務放入隊列,消費者從隊列取出任務處理。 - 優點:解耦生產者與消費者,避免忙等(busy-wait)。
- 使用
-
CompletableFuture
示例:public class AsyncExample {public static void main(String[] args) {CompletableFuture<String> dbFuture = CompletableFuture.supplyAsync(() -> {// 模擬數據庫查詢return "DB Result";});CompletableFuture<String> cacheFuture = CompletableFuture.supplyAsync(() -> {// 模擬緩存查詢return "Cache Result";});dbFuture.thenCombine(cacheFuture, (db, cache) -> {// 合并結果return db + " + " + cache;}).thenAccept(result -> {System.out.println("Final Result: " + result);});} }
6. JVM與并發安全
題目:
- 解釋
volatile
關鍵字的作用原理,并說明它與synchronized
在可見性、原子性和有序性上的區別。 - 請分析以下代碼為何不滿足線程安全,并提出改進方案:
public class VolatileExample {private volatile int counter = 0;public void increment() {counter++;} }
參考答案:
-
volatile
原理:volatile
通過內存屏障(Memory Barrier)確保變量的可見性和禁止指令重排序,但不保證原子性。- 與
synchronized
的區別:- 可見性:兩者均保證可見性。
- 原子性:
synchronized
保證原子性,volatile
不保證。 - 有序性:
volatile
通過禁止指令重排序保證部分有序性,而synchronized
通過鎖的釋放和獲取保證整體有序性。
-
代碼問題:
counter++
操作(read
→increment
→write
)不是原子的,即使counter
是volatile
,多個線程仍可能覆蓋彼此的修改。- 改進方案:
- 使用
synchronized
修飾increment
方法。 - 使用原子類
AtomicInteger
。
- 使用
7. 實戰問題
題目:
- 設計一個線程安全的緩存類,要求:
- 支持并發讀取和寫入。
- 提供過期時間(TTL)功能,自動清除過期數據。
- 支持高并發下的性能優化。
參考答案:
- 設計思路:
- 使用
ConcurrentHashMap
存儲緩存數據,鍵為緩存項的唯一標識,值為帶有TTL的封裝對象。 - 使用
ReentrantReadWriteLock
實現讀寫分離:- 讀操作共享鎖,允許多線程并發讀。
- 寫操作獨占鎖,確保單線程寫入。
- 使用定時任務(如
ScheduledExecutorService
)定期清理過期數據。 - 代碼示例:
public class ConcurrentCache<K, V> {private final ConcurrentHashMap<K, CacheEntry<V>> cache = new ConcurrentHashMap<>();private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);public ConcurrentCache() {scheduler.scheduleAtFixedRate(this::cleanUp, 1, 1, TimeUnit.MINUTES);}public V get(K key) {lock.readLock().lock();try {CacheEntry<V> entry = cache.get(key);if (entry == null || entry.isExpired()) {return null;}return entry.getValue();} finally {lock.readLock().unlock();}}public void put(K key, V value, long ttl, TimeUnit unit) {lock.writeLock().lock();try {cache.put(key, new CacheEntry<>(value, System.currentTimeMillis() + unit.toMillis(ttl)));} finally {lock.writeLock().unlock();}}private void cleanUp() {lock.writeLock().lock();try {cache.entrySet().removeIf(entry -> entry.getValue().isExpired());} finally {lock.writeLock().unlock();}}private static class CacheEntry<V> {private final V value;private final long expireTime;public CacheEntry(V value, long expireTime) {this.value = value;this.expireTime = expireTime;}public V getValue() {return value;}public boolean isExpired() {return System.currentTimeMillis() > expireTime;}} }
- 使用
以上題目覆蓋了Java并發編程的核心知識點,包括線程池、鎖機制、并發工具類、性能優化、死鎖處理以及高階設計模式。通過這些問題,可以全面評估候選人對并發編程的理解和實際應用能力。