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