深入理解Java線程創(chuàng)建方式(最新推薦)
引言:為什么我們需要關注線程?
在多核處理器成為主流的今天,我們手中的手機、電腦甚至智能家居設備都擁有多個計算核心。這意味著,如果我們的程序只能在一個核心上運行,就相當于讓其他核心"閑置",無法充分發(fā)揮硬件性能。想象一下,一個餐廳只有一個服務員,即使廚房有多個廚師,顧客仍然需要排隊等待服務——這就是單線程程序的局限性。
并發(fā)編程正是為了解決這個問題而生,而線程作為并發(fā)編程的基礎單元,理解其工作機制對于編寫高效、穩(wěn)定的應用程序至關重要。作為一名Java開發(fā)者,我深刻體會到,對線程的深入理解往往區(qū)分了初級和高級程序員。在這篇博客中,我將分享我對Java線程的個人理解,從基礎概念到底層實現(xiàn),希望能為你提供有價值的見解。
一、線程與進程:本質(zhì)區(qū)別與內(nèi)在聯(lián)系
在深入線程之前,我們需要從根本上理解線程與進程的區(qū)別。這個理解不能停留在表面,而要深入到操作系統(tǒng)層面。
進程:獨立的王國
進程可以理解為一個獨立的"程序王國",每個王國都有自己獨立的領土(內(nèi)存空間)、資源(打開的文件、網(wǎng)絡連接等)和法律(安全上下文)。操作系統(tǒng)為每個進程分配獨立的虛擬地址空間,這意味著:
進程A無法直接訪問進程B的內(nèi)存數(shù)據(jù)。
進程崩潰通常不會影響其他進程。
進程間通信需要特殊機制(管道、消息隊列、共享內(nèi)存等)。
線程:王國內(nèi)的協(xié)作團隊
線程則是同一個"王國"內(nèi)的不同"工作團隊",它們:
共享王國的資源(內(nèi)存、文件描述符等)。
各自執(zhí)行不同的任務,但可以協(xié)作完成共同目標。
通信成本極低,因為可以直接訪問共享內(nèi)存。
技術視角的深度理解:
從操作系統(tǒng)角度看,進程是資源分配的實體,而線程是CPU調(diào)度的實體。當我們在Java中創(chuàng)建線程時,實際上是在用戶態(tài)創(chuàng)建了一個線程控制塊,然后通過系統(tǒng)調(diào)用在內(nèi)核態(tài)創(chuàng)建對應的內(nèi)核線程(在Linux中通過clone系統(tǒng)調(diào)用)。這就是為什么線程的創(chuàng)建和銷毀比進程輕量得多。
二、Java線程的創(chuàng)建方式:選擇背后的思考
1. 繼承Thread類:簡單但不推薦
class MyThread extends Thread {
@Override
public void run() {
System.out.println("線程執(zhí)行: " + Thread.currentThread().getName());
}
}這種方式看似簡單,但實際上存在設計上的問題。Java是單繼承語言,如果繼承了Thread類,就無法繼承其他類。這違反了"組合優(yōu)于繼承"的設計原則。此外,從任務執(zhí)行的角度看,線程的執(zhí)行體(run方法)和線程本身(Thread類)應該是兩個關注點,這種方式將它們耦合在一起。
2. 實現(xiàn)Runnable接口:推薦的標準做法
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("線程執(zhí)行: " + Thread.currentThread().getName());
}
}為什么這是更好的選擇?
符合面向?qū)ο笤O計原則:任務與執(zhí)行機制分離。
靈活性:可以繼承其他類,實現(xiàn)其他接口。
可復用性:同一個Runnable實例可以被多個線程共享執(zhí)行。
3. 實現(xiàn)Callable接口:需要返回值的場景
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "線程執(zhí)行結果: " + Thread.currentThread().getName();
}
}核心價值:
Callable的出現(xiàn)解決了Runnable無法返回結果和拋出受檢異常的問題。FutureTask作為RunnableFuture接口的實現(xiàn),既可以被Thread執(zhí)行,又可以通過Future接口獲取結果,這種設計體現(xiàn)了接口隔離原則。
4. 線程池方式:生產(chǎn)環(huán)境的必然選擇
ExecutorService executor = Executors.newFixedThreadPool(5); Future<String> future = executor.submit(new MyCallable());
為什么線程池如此重要?直接創(chuàng)建線程的成本很高,包括:
內(nèi)存分配:每個線程需要分配??臻g(默認512KB-1MB)。
系統(tǒng)調(diào)用:需要內(nèi)核參與線程創(chuàng)建。
資源管理:線程數(shù)量無限制增長會導致系統(tǒng)資源耗盡。
線程池通過復用線程、控制并發(fā)數(shù)量、管理生命周期,解決了這些問題。
三、線程狀態(tài)與生命周期:狀態(tài)機的藝術
理解線程的狀態(tài)轉換不僅僅是記住幾個狀態(tài)名稱,而是要理解每個狀態(tài)轉換的條件和意義。
狀態(tài)轉換的深度解析
NEW → RUNNABLE:(線程生命開始)
當調(diào)用start()方法時,線程從NEW狀態(tài)進入RUNNABLE狀態(tài)。這里有個重要細節(jié):start()方法只能調(diào)用一次,否則會拋出IllegalThreadStateException。這是因為線程的生命周期是不可逆的。
RUNNABLE → BLOCKED:(鎖競爭導致)
這種情況通常發(fā)生在 synchronized 同步塊上。當線程A持有鎖,線程B嘗試獲取同一個鎖時,線程B就會進入BLOCKED狀態(tài)。這里的關鍵理解是:BLOCKED狀態(tài)只與同步的monitor鎖相關。
RUNNABLE → WAITING:(主動等待)
有三種方法會導致這種轉換:
Object.wait():釋放鎖并等待,需要其他線程調(diào)用notify()/notifyAll()
Thread.join():等待目標線程終止
LockSupport.park():底層并發(fā)工具使用
RUNNABLE → TIMED_WAITING:(主動等待)
與WAITING類似,但帶有超時時間。這是為了避免永久等待導致的死鎖。
實際開發(fā)中的意義:
理解這些狀態(tài)轉換對于調(diào)試多線程問題至關重要。當線程出現(xiàn)問題時,我們可以通過jstack等工具查看線程狀態(tài),快速定位問題原因。
四、線程同步與線程安全:秩序的藝術
可見性、原子性、有序性
在深入同步機制前,必須理解并發(fā)編程的三個核心問題:
可見性:一個線程對共享變量的修改,其他線程能夠立即看到。由于CPU緩存的存在,線程可能讀取到過期的數(shù)據(jù)。
原子性:一個或多個操作要么全部執(zhí)行成功,要么全部不執(zhí)行,不會出現(xiàn)中間狀態(tài)。
有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。由于指令重排序的存在,實際執(zhí)行順序可能與代碼順序不同。
synchronized的深度理解
public class SynchronizedDemo {
// 實例同步方法:鎖是當前對象實例
public synchronized void instanceMethod() {
// 臨界區(qū)
}
// 靜態(tài)同步方法:鎖是當前類的Class對象
public static synchronized void staticMethod() {
// 臨界區(qū)
}
// 同步代碼塊:可以指定任意對象作為鎖
public void someMethod() {
synchronized(this) {
// 臨界區(qū)
}
}
}synchronized的實現(xiàn)原理:
在字節(jié)碼層面,通過monitorenter和monitorexit指令實現(xiàn)。
每個對象都有一個monitor(監(jiān)視器鎖)與之關聯(lián)。
鎖具有可重入性:同一個線程可以多次獲取同一把鎖。
ReentrantLock:更靈活的鎖機制
public class ReentrantLockDemo {
private final ReentrantLock lock = new ReentrantLock(true); // 公平鎖
public void performTask() {
lock.lock(); // 可以在這里使用lockInterruptibly()支持中斷
try {
// 臨界區(qū)
} finally {
lock.unlock(); // 必須在finally塊中釋放鎖
}
}
}與synchronized的對比:
特性 | synchronized | ReentrantLock |
|---|---|---|
實現(xiàn)機制 | JVM內(nèi)置 | JDK實現(xiàn) |
鎖獲取 | 自動獲取釋放 | 手動控制 |
可中斷 | 不支持 | 支持 |
公平性 | 非公平 | 可選擇公平或非公平 |
條件變量 | 單一 | 多個 |
volatile關鍵字:輕量級的同步
public class VolatileExample {
private volatile boolean shutdown = false;
public void shutdown() {
shutdown = true; // 寫操作具有原子性和可見性
}
public void doWork() {
while (!shutdown) { // 讀操作總能獲取最新值
// 執(zhí)行任務
}
}
}volatile的語義:
可見性:對volatile變量的寫操作會立即刷新到主內(nèi)存。
有序性:禁止指令重排序(內(nèi)存屏障)。
不保證原子性:復合操作(如i++)仍然需要同步。
適用場景:
狀態(tài)標志位。
雙重檢查鎖定模式。
觀察者模式中的狀態(tài)發(fā)布。
五、線程間通信:協(xié)作的智慧
wait/notify機制:經(jīng)典的線程協(xié)作
public class WaitNotifyDemo {
private boolean condition = false;
public synchronized void waitForCondition() throws InterruptedException {
// 必須使用while循環(huán)檢查條件,避免虛假喚醒
while (!condition) {
wait(); // 釋放鎖并等待
}
// 條件滿足,執(zhí)行后續(xù)操作
doSomething();
}
public synchronized void signalCondition() {
condition = true;
notifyAll(); // 通知所有等待線程
}
}wait/notify的使用要點:
必須在同步方法或同步塊中調(diào)用。
總是使用while循環(huán)檢查條件,避免虛假喚醒。
優(yōu)先使用notifyAll()而不是notify(),避免信號丟失。
Condition接口:更精確的線程控制
public class ConditionDemo {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean ready = false;
public void await() throws InterruptedException {
lock.lock();
try {
while (!ready) {
condition.await(); // 等待條件
}
} finally {
lock.unlock();
}
}
public void signal() {
lock.lock();
try {
ready = true;
condition.signal(); // 通知等待線程
} finally {
lock.unlock();
}
}
}Condition的優(yōu)勢:
一個鎖可以關聯(lián)多個Condition。
支持更靈活的等待條件。
可以精確喚醒特定類型的等待線程。
六、線程池的核心原理:池化技術的典范
線程池的架構設計
線程池采用了生產(chǎn)者-消費者模式:
生產(chǎn)者:提交任務的線程
消費者:工作線程
緩沖區(qū):工作隊列
public class ThreadPoolAnatomy {
// ThreadPoolExecutor的核心構造參數(shù)
ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心線程數(shù):池中保持的線程數(shù)量
10, // 最大線程數(shù):池中允許的最大線程數(shù)量
60L, // 保持時間:超出核心線程數(shù)的空閑線程存活時間
TimeUnit.SECONDS, // 時間單位
new LinkedBlockingQueue<>(100), // 工作隊列:存儲待執(zhí)行任務
Executors.defaultThreadFactory(), // 線程工廠:創(chuàng)建新線程
new ThreadPoolExecutor.AbortPolicy() // 拒絕策略:無法處理任務時的策略
);
}任務執(zhí)行流程的深度解析
任務提交:調(diào)用execute()或submit()方法
核心線程檢查:如果當前線程數(shù) < corePoolSize,創(chuàng)建新線程
隊列檢查:如果線程數(shù) ≥ corePoolSize,嘗試將任務放入隊列
最大線程檢查:如果隊列已滿且線程數(shù) < maximumPoolSize,創(chuàng)建新線程
拒絕策略:如果隊列已滿且線程數(shù) ≥ maximumPoolSize,執(zhí)行拒絕策略
這個流程的重要性在于:它決定了線程池的行為特性。理解這個流程有助于我們根據(jù)具體場景配置合適的參數(shù)。
拒絕策略的四種選擇
AbortPolicy(默認):拋出RejectedExecutionException。
CallerRunsPolicy:由調(diào)用者線程執(zhí)行任務。
DiscardPolicy:靜默丟棄任務。
DiscardOldestPolicy:丟棄隊列中最老的任務,然后重試。
七、常見問題與最佳實踐:經(jīng)驗的結晶
死鎖:四大必要條件
死鎖的發(fā)生需要同時滿足四個條件:
互斥條件:資源不能被共享
持有并等待:線程持有資源并等待其他資源
不可剝奪:資源只能由持有線程釋放
循環(huán)等待:存在線程資源的循環(huán)等待鏈
預防死鎖的策略:
按固定順序獲取鎖
使用tryLock()帶有超時機制
使用更高級的并發(fā)工具
public class DeadlockPrevention {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized(lock1) {
// 一些操作
synchronized(lock2) {
// 臨界區(qū)
}
}
}
public void method2() {
synchronized(lock1) { // 使用與method1相同的鎖順序
// 一些操作
synchronized(lock2) {
// 臨界區(qū)
}
}
}
}上下文切換:看不見的性能殺手
上下文切換的成本包括:
直接成本:保存和恢復線程上下文。
間接成本:緩存失效、TLB刷新。
優(yōu)化建議:
避免創(chuàng)建過多線程。
使用線程池復用線程。
減少鎖競爭(鎖細化、使用并發(fā)集合)。
最佳實踐總結
命名線程:便于調(diào)試和監(jiān)控
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("worker-thread-%d")
.build();正確處理異常:
executor.submit(() -> {
try {
// 任務邏輯
} catch (Exception e) {
// 記錄日志,不要吞掉異常
logger.error("Task execution failed", e);
}
});資源清理:
executor.shutdown();
try {
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}八、Java內(nèi)存模型(JMM):并發(fā)編程的理論基礎
happens-before關系
happens-before是JMM的核心概念,它定義了操作之間的可見性關系:
程序次序規(guī)則:線程內(nèi)按照代碼順序執(zhí)行。
監(jiān)視器鎖規(guī)則:解鎖操作happens-before后續(xù)的加鎖操作。
volatile變量規(guī)則:寫操作happens-before后續(xù)的讀操作。
線程啟動規(guī)則:Thread.start()happens-before線程內(nèi)的任何操作。
線程終止規(guī)則:線程中的所有操作happens-before其他線程檢測到該線程已經(jīng)終止。
內(nèi)存屏障
為了實現(xiàn)happens-before關系,JVM在適當?shù)奈恢貌迦雰?nèi)存屏障:
LoadLoad屏障:禁止讀操作重排序。
StoreStore屏障:禁止寫操作重排序。
LoadStore屏障:禁止讀后寫重排序。
StoreLoad屏障:禁止寫后讀重排序。
理解這些底層機制有助于我們寫出正確性更高的并發(fā)代碼。
到此這篇關于深入理解Java線程的文章就介紹到這了,更多相關java多線程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring?Cloud?Gateway中netty線程池優(yōu)化示例詳解
這篇文章主要介紹了Spring?Cloud?Gateway中netty線程池優(yōu)化示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-07-07
SpringBoot整合MyBatis Plus實現(xiàn)基本CRUD與高級功能
Spring Boot是一款用于快速構建Spring應用程序的框架,而MyBatis Plus是MyBatis的增強工具,本文將詳細介紹如何在Spring Boot項目中整合MyBatis Plus,并展示其基本CRUD功能以及高級功能的實現(xiàn)方式,需要的朋友可以參考下2024-02-02
java父子節(jié)點parentid樹形結構數(shù)據(jù)的規(guī)整
這篇文章主要介紹了java父子節(jié)點parentid樹形結構數(shù)據(jù)的規(guī)整,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07
SpringBoot調(diào)用第三方WebService接口的操作技巧(.wsdl與.asmx類型)
這篇文章主要介紹了SpringBoot調(diào)第三方WebService接口的操作代碼(.wsdl與.asmx類型 ),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08

