線程基本機制
- 進程包含多個線程,共享堆/方法區,私有棧/程序計數器
- 單核通過時間片輪轉實現併發,多核實現真並行
- 阻塞(I/O、鎖、睡眠)釋放 CPU,提升整體吞吐
四種創建線程方式
1. 實現 Runnable 接口(推薦基礎方式)
public class LiftOff implements Runnable {
private int countDown = 10;
@Override public void run() {
while (countDown-- > 0) {
System.out.println(Thread.currentThread() + " #" + countDown);
Thread.yield(); // 讓出 CPU,非強制
}
}
}
// 啟動
new Thread(new LiftOff()).start();
2. 繼承 Thread 類(不推薦,單繼承限制)
public class SimpleThread extends Thread {
private int countDown = 5;
public SimpleThread() { super(String.valueOf(++threadCount)); start(); }
@Override public void run() {
while (true) { System.out.println(this); if (--countDown == 0) return; }
}
}
3. 提交給 ExecutorService(生產環境首選)
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) exec.execute(new LiftOff());
exec.shutdown(); // 優雅關閉
// 其他線程池
Executors.newFixedThreadPool(n); // 固定大小
Executors.newSingleThreadExecutor(); // 單線程串行
Executors.newScheduledThreadPool(n); // 定時/周期任務
4. 實現 Callable 接口(需返回值/拋異常)
class TaskWithResult implements Callable {
private int id;
public TaskWithResult(int id) { this.id = id; }
@Override public String call() { return "result of " + id; }
}
ExecutorService exec = Executors.newCachedThreadPool();
List> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) futures.add(exec.submit(new TaskWithResult(i)));
for (Future f : futures) {
try { System.out.println(f.get()); } // 阻塞獲取結果
catch (Exception e) { e.printStackTrace(); }
}
exec.shutdown();
守護線程
- 後台服務線程(GC、JIT 編譯),優先級極低
thread.setDaemon(true) 必須在 start() 前調用
- 所有非守護線程結束 → JVM 退出 → 殺死所有守護線程
- 守護線程創建的線程默認也是守護線程
for (int i = 0; i < 10; i++) {
Thread d = new Thread(new SimpleDaemons());
d.setDaemon(true);
d.start();
}
Thread.sleep(175); // 主線程結束,守護線程隨之終止
線程加入
t1.join():當前線程等待 t1 終止
t1.join(millis):超時返回
- 可被
interrupt() 中斷,拋出 InterruptedException
Sleeper sleepy = new Sleeper("Sleepy", 1500);
Joiner dopey = new Joiner("Dopey", sleepy); // dopey.join(sleepy)
sleepy.join(); // main 等待 sleepy
// 輸出順序:Grumpy interrupted → Doc join completed → Sleepy awakened → Dopey join completed → main continue
異常處理機制
問題:main try-catch 無法捕獲子線程異常
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(() -> { throw new RuntimeException(); });
// 異常丟失,只在控制台打印堆棧
方案 1:線程工廠設置 UncaughtExceptionHandler (JDK 5+)
class HandlerThreadFactory implements ThreadFactory {
@Override public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler((thread, throwable) ->
System.out.println("Thread " + thread + " crashed: " + throwable));
return t;
}
}
ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());
方案 2:全局默認處理器
Thread.setDefaultUncaughtExceptionHandler((t, e) ->
System.out.println("Global handler: " + t + " -> " + e));
ExecutorService exec = Executors.newCachedThreadPool();
總結建議
- 業務線程統一用
ExecutorService 管理,避免顯式 new Thread
- 線程池大小根據任務類型調整:CPU 密集 ~ 核數,IO 密集 ~ 2*核數
- 關鍵異常必須通過
UncaughtExceptionHandler 記錄,防止靜默失敗
- 優雅關閉:
shutdown() → awaitTermination() → shutdownNow()