一文詳解如何在Java中啟動線程
1. 簡介
今天要跟大家聊一聊Java多線程的原理和使用方式,Java多線程是提高程序性能的重要機制。了解線程生命周期、同步機制、線程池和線程間通信等概念,可以幫助我們編寫出高效、可靠的多線程程序。

Java 線程的生命周期可以分為以下幾個狀態(tài):
1. 新建 (New): 當使用 new Thread() 創(chuàng)建線程對象時,線程處于新建狀態(tài)。此時線程尚未啟動,還沒有分配系統(tǒng)資源。
2. 就緒 (Runnable): 當調(diào)用線程對象的 start() 方法時,線程進入就緒狀態(tài)。此時線程已準備好運行,但尚未獲得 CPU 時間片,需要等待操作系統(tǒng)分配。
3. 運行 (Running): 線程獲得 CPU 時間片,開始執(zhí)行線程體代碼,進入運行狀態(tài)。
4. 阻塞 (Blocked): 當線程遇到以下情況時,會進入阻塞狀態(tài):
- 等待 (Waiting): 線程調(diào)用 Object.wait() 方法等待某個特定事件發(fā)生。
- 睡眠 (Sleeping): 線程調(diào)用 Thread.sleep() 方法進入睡眠狀態(tài),等待一段時間后自動恢復(fù)運行。
- I/O 阻塞: 線程等待 I/O 操作完成,例如讀取文件或網(wǎng)絡(luò)數(shù)據(jù)。
- 鎖阻塞: 線程試圖獲取某個鎖,但鎖被其他線程占用,導(dǎo)致阻塞。
5. 終止 (Terminated): 線程執(zhí)行完 run() 方法中的代碼,或者遇到異常而退出,進入終止狀態(tài)。
線程狀態(tài)轉(zhuǎn)換:
- 新建 -> 就緒: 調(diào)用 start() 方法。
- 就緒 -> 運行: 操作系統(tǒng)分配 CPU 時間片。
- 運行 -> 阻塞: 線程遇到等待、睡眠、I/O 阻塞、鎖阻塞等情況。
- 阻塞 -> 就緒: 等待的事件發(fā)生、睡眠時間結(jié)束、I/O 操作完成、獲得鎖。
- 運行 -> 終止: run() 方法執(zhí)行完畢、遇到異常。
其他重要概念:
- 守護線程 (Daemon Thread): 守護線程是為其他線程服務(wù)的線程,例如垃圾回收線程。當所有非守護線程都終止后,守護線程也會自動終止。
- 線程優(yōu)先級 (Thread Priority): 線程可以設(shè)置優(yōu)先級,優(yōu)先級高的線程更容易獲得 CPU 時間片。
2. 運行線程的基礎(chǔ)知識
我們可以利用Thread框架輕松的編寫一些在并行線程中運行的邏輯。
讓我們嘗試一個基本的例子,通過擴展Thread類:
public class NewThread extends Thread {
public void run() {
long startTime = System.currentTimeMillis();
int i = 0;
while (true) {
System.out.println(this.getName() + ": New Thread is running..." + i++);
try {
//Wait for one sec so it doesn't print too fast
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
...
}
}
}
現(xiàn)在我們編寫第二個類來初始化并啟動我們的線程:
public class SingleThreadExample {
public static void main(String[] args) {
NewThread t = new NewThread();
t.start();
}
}
我們應(yīng)該 對處于NEW狀態(tài)(相當于未啟動)的線程 調(diào)用start()方法。否則,Java 將拋出IllegalThreadStateException異常的實例。
現(xiàn)在假設(shè)我們需要啟動多個線程:
public class MultipleThreadsExample {
public static void main(String[] args) {
NewThread t1 = new NewThread();
t1.setName("MyThread-1");
NewThread t2 = new NewThread();
t2.setName("MyThread-2");
t1.start();
t2.start();
}
}
我們的代碼看起來仍然非常簡單并且與我們在網(wǎng)上找到的示例非常相似。
當然,這與可用于生產(chǎn)的代碼還相去甚遠,以正確的方式管理資源至關(guān)重要,以避免過多的上下文切換或過多的內(nèi)存使用。
因此,為了做好生產(chǎn)準備,我們現(xiàn)在需要編寫額外的樣板來處理:
- 持續(xù)創(chuàng)建新線程
- 并發(fā)活動線程數(shù)
- 線程釋放:對于守護線程來說非常重要,可以避免泄漏
如果我們愿意,我們可以為所有這些情況甚至更多情況編寫自己的代碼,但我們?yōu)槭裁匆匦掳l(fā)明輪子呢?
3. ExecutorService框架
ExecutorService實現(xiàn)了線程池設(shè)計模式(也稱為復(fù)制工作器或工作組模型),并負責我們上面提到的線程管理,此外它還添加了一些非常有用的功能,如線程可重用性和任務(wù)隊列 。
線程可重用性尤其重要:在大型應(yīng)用程序中,分配和釋放許多線程對象會產(chǎn)生大量內(nèi)存管理開銷。
利用工作線程,我們可以最大限度地減少線程創(chuàng)建造成的開銷。 為了簡化池配置,ExecutorService帶有一個簡單的構(gòu)造函數(shù)和一些自定義選項,例如隊列的類型、最小和最大線程數(shù)及其命名約定。
4. 使用執(zhí)行器啟動任務(wù)
有了這個強大的框架,我們可以把思維從啟動線程轉(zhuǎn)變?yōu)樘峤蝗蝿?wù)。
讓我們看看如何向執(zhí)行器提交異步任務(wù):
ExecutorService executor = Executors.newFixedThreadPool(10);
...
executor.submit(() -> {
new Task();
});
我們可以使用兩種方法:execute(不返回任何內(nèi)容)和submit(返回封裝了計算結(jié)果的Future )。
5. 使用CompletableFutures啟動任務(wù)
要從Future對象中檢索最終結(jié)果,我們可以使用 該對象中可用的get 方法,但這會阻塞父線程,直到計算結(jié)束。
或者,我們可以通過在任務(wù)中添加更多邏輯來避免阻塞,但我們必須增加代碼的復(fù)雜性。 Java 1.8 在Future構(gòu)造之上引入了一個新框架,以便更好地處理計算結(jié)果:CompletableFuture。
CompletableFuture 實現(xiàn)了CompletableStage,它增加了大量方法來附加回調(diào)并避免了在結(jié)果準備好后運行操作所需的所有管道。
提交任務(wù)的實現(xiàn)就簡單多了:
CompletableFuture.supplyAsync(() -> "Hello");
supplyAsync接受一個Supplier,其中包含我們想要異步執(zhí)行的代碼——在我們的例子中是 lambda 參數(shù)。
該任務(wù)現(xiàn)在隱式提交給 ForkJoinPool.commonPool() ,或者我們可以指定我們喜歡的Executor作為第二個參數(shù)。
6. 運行延遲或周期性任務(wù)
在處理復(fù)雜的 Web 應(yīng)用程序時,我們可能需要在特定時間(或許是定期)運行任務(wù)。
Java 有一些工具可以幫助我們運行延遲或重復(fù)的操作:
- 定時器
- java.util.concurrent.ScheduledThreadPoolExecutor
6.1. 計時器
計時器是一種用于安排任務(wù)在后臺線程中將來執(zhí)行的工具。
任務(wù)可以被安排一次性執(zhí)行,或者定期重復(fù)執(zhí)行。
如果我們想在延遲一秒鐘后運行任務(wù),讓我們看看代碼是什么樣的:
TimerTask task = new TimerTask() {
public void run() {
System.out.println("Task performed on: " + new Date() + "n"
+ "Thread's name: " + Thread.currentThread().getName());
}
};
Timer timer = new Timer("Timer");
long delay = 1000L;
timer.schedule(task, delay);復(fù)制
現(xiàn)在讓我們添加一個重復(fù)的計劃:
timer.scheduleAtFixedRate(repeatedTask, delay, period);復(fù)制
這一次,任務(wù)將在指定的延遲后運行,并且在經(jīng)過一段時間后重復(fù)運行。
6.2. ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor具有與 Timer類 類似的方法:
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2); ScheduledFuture<Object> resultFuture = executorService.schedule(callableTask, 1, TimeUnit.SECONDS);
為了結(jié)束我們的示例,我們使用 scheduleAtFixedRate() 來執(zhí)行重復(fù)任務(wù):
ScheduledFuture<Object> resultFuture = executorService.scheduleAtFixedRate(runnableTask, 100, 450, TimeUnit.MILLISECONDS);
上述代碼將在初始延遲 100 毫秒后執(zhí)行一項任務(wù),之后每 450 毫秒執(zhí)行一次相同的任務(wù)。
如果處理器不能在下一次發(fā)生之前及時完成處理任務(wù),則 ScheduledExecutorService 將等到當前任務(wù)完成后再開始下一個任務(wù)。
為了避免這段等待時間,我們可以使用 scheduleWithFixedDelay() ,正如其名稱所述,它可以保證任務(wù)迭代之間的固定長度延遲。
6.3. 哪個工具更好?
如果我們運行上述例子,計算的結(jié)果看起來是一樣的。
那么, 我們?nèi)绾芜x擇正確的工具?
當一個框架提供多種選擇時,了解底層技術(shù)以做出明智的決定非常重要。
讓我們嘗試更深入地探究一下。
計時器:
- 不提供實時保證:它使用 Object.wait (long) 方法來安排任務(wù)
- 只有一個后臺線程,因此任務(wù)按順序運行,長時間運行的任務(wù)可能會延遲其他任務(wù)
- TimerTask中拋出的運行時異常會殺死唯一可用的線程,從而殺死Timer
ScheduledThreadPoolExecutor:
- 可以配置任意數(shù)量的線程
- 可以利用所有可用的 CPU 核心
- 捕獲運行時異常并讓我們根據(jù)需要處理它們(通過重寫ThreadPoolExecutor的 afterExecute方法)**
- 取消引發(fā)異常的任務(wù),同時讓其他任務(wù)繼續(xù)運行
- 依靠操作系統(tǒng)調(diào)度系統(tǒng)來跟蹤時區(qū)、延遲、太陽時等。
- 如果我們需要多個任務(wù)之間的協(xié)調(diào),則提供協(xié)作 API,例如等待所有提交的任務(wù)完成
- 提供更好的 API 來管理線程生命周期
7. Future和ScheduledFuture之間的區(qū)別
在我們的代碼示例中,我們可以觀察到ScheduledThreadPoolExecutor 返回特定類型的Future:ScheduledFuture。
ScheduledFuture 擴展了Future和Delayed接口,因此繼承了額外的方法getDelay ,該方法返回與當前任務(wù)相關(guān)的剩余延遲。它由RunnableScheduledFuture擴展,并添加了一個方法來檢查任務(wù)是否是周期性的。
ScheduledThreadPoolExecutor 通過內(nèi)部類ScheduledFutureTask實現(xiàn)所有這些構(gòu)造,并使用它們來控制任務(wù)生命周期。
8. 結(jié)論
Java 多線程在各種應(yīng)用場景中都發(fā)揮著重要作用,可以顯著提高程序效率和響應(yīng)速度。以下列舉一些常見的 Java 多線程使用場景:
1. 并發(fā)處理:
- 網(wǎng)絡(luò)應(yīng)用程序: 多個線程可以同時處理來自多個客戶端的請求,提高服務(wù)器的吞吐量和響應(yīng)速度。
- 文件處理: 多個線程可以同時讀取、寫入或處理多個文件,加速文件操作。
- 數(shù)據(jù)庫操作: 多個線程可以同時執(zhí)行數(shù)據(jù)庫查詢或更新操作,提高數(shù)據(jù)庫的并發(fā)性能。
2. 用戶界面響應(yīng):
- 圖形界面應(yīng)用程序: 多個線程可以將耗時的操作(如圖片加載、數(shù)據(jù)處理)放到后臺執(zhí)行,避免阻塞主線程,保持用戶界面的流暢響應(yīng)。
- 游戲開發(fā): 多個線程可以同時處理游戲邏輯、渲染圖形、播放聲音等任務(wù),提高游戲流暢度和響應(yīng)速度。
3. 并行計算:
- 科學(xué)計算: 多個線程可以同時執(zhí)行復(fù)雜的數(shù)學(xué)計算,加速計算過程。
- 數(shù)據(jù)分析: 多個線程可以同時處理大量數(shù)據(jù),加快數(shù)據(jù)分析速度。
- 機器學(xué)習(xí): 多個線程可以同時訓(xùn)練模型,提高模型訓(xùn)練效率。
4. 任務(wù)調(diào)度:
- 任務(wù)管理系統(tǒng): 多個線程可以同時執(zhí)行不同的任務(wù),提高任務(wù)處理效率。
- 定時任務(wù): 多個線程可以按照指定的時間間隔或時間點執(zhí)行任務(wù)。
5. 異步操作:
- 網(wǎng)絡(luò)通信: 使用多線程可以異步發(fā)送和接收網(wǎng)絡(luò)數(shù)據(jù),提高網(wǎng)絡(luò)通信效率。
- 文件上傳/下載: 多線程可以異步執(zhí)行文件上傳/下載操作,避免阻塞主線程。
6. 其他應(yīng)用場景:
- 多線程測試: 可以使用多線程模擬多個用戶同時訪問系統(tǒng),進行壓力測試。
- 并發(fā)編程框架: 許多并發(fā)編程框架(如 Akka、RxJava)都是基于多線程實現(xiàn)的。
以上就是一文詳解如何在Java中啟動線程的詳細內(nèi)容,更多關(guān)于Java啟動線程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java多線程并發(fā)executorservice(任務(wù)調(diào)度)類
這篇文章主要介紹了線程并發(fā)ScheduledExecutorService類,設(shè)置 ScheduledExecutorService ,2秒后,在 1 分鐘內(nèi)每 10 秒鐘蜂鳴一次2014-01-01
淺析Android系統(tǒng)中HTTPS通信的實現(xiàn)
這篇文章主要介紹了淺析Android系統(tǒng)中HTTPS通信的實現(xiàn),實現(xiàn)握手的源碼為Java語言編寫,需要的朋友可以參考下2015-07-07
Java中String字符串轉(zhuǎn)具體對象的幾種常用方式
String對象可以用來存儲任何字符串類型的數(shù)據(jù),包括HTML、XML等格式的字符串,下面這篇文章主要給大家介紹了關(guān)于JavaString字符串轉(zhuǎn)具體對象的幾種常用方式,需要的朋友可以參考下2024-03-03
SpringBoot通過計劃任務(wù)發(fā)送郵件提醒的代碼詳解
在實際線上項目中,有不斷接受到推送方發(fā)來的數(shù)據(jù)場景,而且是不間斷的發(fā)送,如果忽然間斷了,應(yīng)該是出問題了,需要及時檢查原因,這種情況比較適合用計劃任務(wù)做檢查判斷,出問題發(fā)郵件提醒,本文給大家介紹了SpringBoot通過計劃任務(wù)發(fā)送郵件提醒,需要的朋友可以參考下2024-11-11
Nacos與SpringBoot實現(xiàn)配置管理的開發(fā)實踐
在微服務(wù)架構(gòu)中,配置管理是一個核心組件,而Nacos為此提供了一個強大的解決方案,本文主要介紹了Nacos與SpringBoot實現(xiàn)配置管理的開發(fā)實踐,具有一定的參考價值2023-08-08

