一、基礎概念
1. 并行與并發的區別?
- 并行:多個任務在多個CPU核心上同時執行(物理上同時)。
- 并發:多個任務在單CPU核心上交替執行(邏輯上同時)。
- 類比:并行是多個窗口同時服務,并發是一個窗口輪流服務。
2. 線程的創建方式?
- 繼承
Thread
類:重寫run()
方法,通過start()
啟動。 - 實現
Runnable
接口:更靈活,避免單繼承限制。 - 實現
Callable
接口:支持返回值和異常處理,配合FutureTask使用。
3. 線程的狀態?
狀態 | 說明 |
---|---|
NEW | 線程被創建但未啟動 |
RUNNABLE | 線程正在執行或等待CPU資源 |
BLOCKED | 線程等待鎖(進入同步代碼塊) |
WAITING | 線程調用wait() /join() 后等待喚醒 |
TIMED_WAITING | 超時等待(如sleep(long) ) |
TERMINATED | 線程執行完畢或異常終止 |
4. sleep()
與wait()
的區別?
特性 | sleep() | wait() |
---|---|---|
所屬類 | Thread | Object |
鎖行為 | 不釋放鎖 | 釋放鎖 |
喚醒方式 | 超時自動喚醒 | 需其他線程調用notify() 喚醒 |
使用場景 | 線程休眠 | 線程間通信 |
5. 進程與線程的區別?
- 進程:資源分配的最小單位(如內存、文件句柄)。
- 線程:調度的最小單位,共享進程資源(如堆、方法區),但有獨立棧和寄存器。
- 協程:比線程更輕量級(如Kotlin的Coroutine)。
6.為什么用start()而非直接調用run()?
- start()會創建新線程并執行run(),而直接調用run()僅在當前線程執行,無并發效果。
二、ThreadLocal
7. ThreadLocal的作用?
- 線程隔離:為每個線程提供獨立變量副本,避免共享數據沖突。
- 典型場景:用戶會話管理、數據庫連接上下文傳遞。
8. ThreadLocal的實現原理?
- 線程私有Map:每個線程維護一個
ThreadLocal.ThreadLocalMap
,鍵為ThreadLocal
對象,值為線程變量。 - 弱引用:鍵使用弱引用,防止內存泄漏。
9. ThreadLocal內存泄漏問題?
- 原因:線程未及時調用
remove()
,導致Entry
的value
強引用無法被回收。 - 解決:使用
try-finally
確保調用remove()
。
三、Java內存模型(JMM)
9.JMM的核心是什么?
- 定義線程間共享變量的訪問規則,解決可見性、有序性、原子性問題。
- 主內存:共享變量存儲區。
- 本地內存:線程私有的共享變量副本(抽象概念,對應CPU緩存等)。
10. JMM的三大特性?
- 原子性:操作不可分割(如
synchronized
保證代碼塊原子性)。 - 可見性:一個線程修改的值對其他線程立即可見(
volatile
或synchronized
)。 - 有序性:禁止指令重排序(
volatile
通過內存屏障實現)。
11. volatile
的作用?
- 可見性:強制將修改刷新到主內存,禁止緩存。
- 有序性:通過內存屏障禁止指令重排序。
12. volatile的作用及實現原理?
- 作用:保證可見性和禁止指令重排。
- 原理:
- 寫操作后插入Store-Barrier,強制刷新主內存。
- 讀操作前插入Load-Barrier,強制從主內存讀取。
四、鎖機制
13. synchronized的使用方式?
- 修飾方法:鎖對象實例(非靜態方法)或類(靜態方法)。
- 修飾代碼塊:指定鎖對象,更細粒度控制。
14. synchronized的實現原理?
- Monitor對象:每個Java對象關聯一個監視器,線程通過獲取Monitor實現互斥。
- 鎖升級:偏向鎖 → 輕量級鎖 → 重量級鎖(基于CAS和自旋優化)。
15. synchronized與ReentrantLock的區別?
特性 | synchronized | ReentrantLock |
---|---|---|
可重入 | 自動支持 | 需手動釋放(unlock() ) |
公平性 | 非公平 | 支持公平鎖(通過構造函數) |
鎖獲取 | 阻塞等待 | 支持tryLock() 非阻塞獲取 |
條件變量 | 僅wait() /notify() | 支持Condition 多條件變量 |
15. CAS的原理及問題?
- 原理:Compare-And-Swap,通過原子指令實現無鎖操作。
- 問題:
- ABA問題:通過版本號(如
AtomicStampedReference
)解決。 - 循環開銷:長時間自旋消耗CPU。
- ABA問題:通過版本號(如
五、并發工具類
16. CountDownLatch與CyclicBarrier的區別?
- CountDownLatch:計數器遞減至0時釋放所有等待線程(一次性)。
- CyclicBarrier:所有線程到達屏障后繼續執行(可重用)。
17. Semaphore的作用?
- 控制并發量:允許指定數量的線程同時訪問資源。
- 應用場景:限流、資源池管理。
六、線程池
18. 線程池的工作流程?
- 任務提交到線程池。
- 核心線程處理任務,空閑則創建新線程(未達最大線程數)。
- 任務隊列已滿且線程數達最大值時,觸發拒絕策略。
提交任務 → 核心線程執行 → 隊列緩沖 → 最大線程處理 → 拒絕策略。
19. 線程池參數配置?
- corePoolSize:核心線程數。
- maximumPoolSize:最大線程數。
- keepAliveTime:空閑線程存活時間。
- workQueue:阻塞隊列(如
LinkedBlockingQueue
)。 - handler:拒絕策略(如
AbortPolicy
)。
20. 線程池的拒絕策略?
- AbortPolicy:拋異常(默認)。
- CallerRunsPolicy:任務回退到調用線程執行。
- DiscardPolicy:靜默丟棄。
- DiscardOldestPolicy:丟棄隊列中最老的任務。
七、高級主題
21. 死鎖的條件及避免?
- 條件:互斥、持有并等待、不可搶占、循環等待。
- 避免:
- 按順序加鎖。
- 設置超時時間。
- 使用
ReentrantLock
的tryLock()
。
22. ConcurrentHashMap的線程安全機制?
- 分段鎖(JDK7):將數據分段,鎖粒度細化。
- CAS+synchronized(JDK8):數組+鏈表/紅黑樹結構,CAS保證原子性,synchronized保證同步。
23.CAS的問題及解決?
- 問題:ABA問題、循環開銷大。
- 解決:AtomicStampedReference解決ABA,結合volatile減少循環。
八、代碼實戰
24. 手寫雙重校驗單例模式?
public class Singleton {private static volatile Singleton instance; // 禁止指令重排private Singleton() {}public static Singleton getInstance() {if (instance == null) { // 第一次檢查synchronized (Singleton.class) { // 加鎖if (instance == null) { // 第二次檢查instance = new Singleton(); // 初始化對象}}}return instance;}
}
volatile的必要性:
- 防止instance = new Singleton()的指令重排(先分配內存,再初始化對象,最后賦值引用)。
- 若未使用volatile,其他線程可能在初始化完成前拿到未完全構造的對象。
25.線程的創建方式對比
// 繼承Thread類
public class ThreadDemo extends Thread {@Overridepublic void run() {System.out.println("Thread running");}
}// 實現Runnable接口
public class RunnableDemo implements Runnable {@Overridepublic void run() {System.out.println("Runnable running");}
}// 實現Callable接口
public class CallableDemo implements Callable<String> {@Overridepublic String call() throws Exception {return "Callable result";}
}// 使用示例
public static void main(String[] args) throws Exception {// Threadnew ThreadDemo().start();// Runnablenew Thread(new RunnableDemo()).start();// Callable + FutureTaskFutureTask<String> futureTask = new FutureTask<>(new CallableDemo());new Thread(futureTask).start();System.out.println("Result: " + futureTask.get());
}
原理對比:
- Thread類本身實現了Runnable接口,通過繼承方式耦合度較高。
- Runnable和Callable將任務與線程解耦,支持更靈活的擴展。
- Callable通過FutureTask包裝后,可通過get()方法獲取異步結果。
26.ThreadLocal內存泄漏
public class ThreadLocalDemo {private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {threadLocal.set("value");// 未調用threadLocal.remove(),可能導致內存泄漏}
}
原理分析:
- ThreadLocalMap的Entry以ThreadLocal為鍵(弱引用),若未手動remove(),當ThreadLocal對象被回收后,value仍被強引用在Entry中。
- 解決方案:
try {threadLocal.set("value");// 業務邏輯
} finally {threadLocal.remove(); // 在finally中確保清理
}
27.volatile的內存屏障
public class VolatileDemo {private volatile int x = 0;public void write() {x = 1; // 寫操作后插入Store-Barrier}public void read() {int y = x; // 讀操作前插入Load-Barrier}
}
內存屏障原理:
- Store-Barrier:確保屏障前的寫操作全部刷新到主內存。
- Load-Barrier:確保屏障后的讀操作從主內存獲取最新值。
- 禁止指令重排:通過內存屏障阻止編譯器和CPU對volatile變量操作的重排序。
28.synchronized vs ReentrantLock
// synchronized示例
public class SynchronizedDemo {public synchronized void method() {// 同步代碼}
}// ReentrantLock示例
public class ReentrantLockDemo {private final ReentrantLock lock = new ReentrantLock();public void method() {lock.lock();try {// 同步代碼} finally {lock.unlock();}}
}
29.CountDownLatch vs CyclicBarrier
// CountDownLatch示例
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {CountDownLatch latch = new CountDownLatch(2);new Thread(() -> {System.out.println("Task1 done");latch.countDown();}).start();new Thread(() -> {System.out.println("Task2 done");latch.countDown();}).start();latch.await(); // 等待兩個任務完成System.out.println("All tasks done");}
}// CyclicBarrier示例
public class CyclicBarrierDemo {public static void main(String[] args) {CyclicBarrier barrier = new CyclicBarrier(2, () -> {System.out.println("Barrier reached");});new Thread(() -> {try {System.out.println("Task1 ready");barrier.await();System.out.println("Task1 continue");} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}).start();new Thread(() -> {try {System.out.println("Task2 ready");barrier.await();System.out.println("Task2 continue");} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}}).start();}
}
核心區別:
- CountDownLatch:計數器遞減到0后不可重置,用于“等待多個任務完成”。
- CyclicBarrier:計數器達到閾值后重置,可循環使用,用于“多個線程同步執行”。
30.線程池工作流程
ExecutorService executor = new ThreadPoolExecutor(2, // corePoolSize4, // maximumPoolSize30, // keepAliveTimeTimeUnit.SECONDS,new ArrayBlockingQueue<>(10), // workQueueExecutors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy() // 拒絕策略
);
工作流程:
- 提交任務 → 核心線程執行(未達corePoolSize時創建新線程)。
- 核心線程滿 → 任務存入隊列(如ArrayBlockingQueue)。
- 隊列滿 → 非核心線程執行(不超過maximumPoolSize)。
- 所有線程忙且隊列滿 → 觸發拒絕策略(如AbortPolicy拋異常)。
31.CAS的ABA問題
AtomicInteger atomicInt = new AtomicInteger(100);
// 線程A:
int oldValue = atomicInt.get();
// 假設線程B將值改為101,再改回100
atomicInt.compareAndSet(oldValue, 200); // CAS成功,但值已被篡改
解決方案:
AtomicStampedReference<Integer> stampedRef = new AtomicStampedReference<>(100, 0);
// 線程A:
int[] stampHolder = new int[1];
int oldValue = stampedRef.get(stampHolder);
int oldStamp = stampHolder[0];
// 線程B修改值并增加版本號
stampedRef.compareAndSet(oldValue, 101, oldStamp, oldStamp + 1);
// 線程A再次嘗試CAS:
stampedRef.compareAndSet(oldValue, 200, oldStamp, oldStamp + 1); // 失敗(版本號不匹配)
總結:以上題目覆蓋Java并發編程核心知識點,建議重點掌握線程安全實現、鎖優化、JMM原理、線程池調優等模塊。面試時需結合源碼和實際場景說明設計原理,體現對底層機制的理解。