Java創(chuàng)建線程的方式-如何創(chuàng)建-有什么創(chuàng)建方法
摘要
在Java并發(fā)編程中,創(chuàng)建線程是基礎(chǔ)也是核心操作。無論是新手入門還是資深開發(fā)者優(yōu)化性能,都繞不開“怎么創(chuàng)建線程”這個問題。Java提供了多種創(chuàng)建線程的方式:繼承Thread類、實現(xiàn)Runnable接口、實現(xiàn)Callable接口配合FutureTask、使用線程池等。每種方式各有優(yōu)缺點和適用場景——比如Thread類簡單但受限于單繼承,Runnable接口解耦性強,Callable能獲取返回值,而線程池則是企業(yè)級開發(fā)的“性能擔(dān)當”。本文會從“是什么、怎么用、什么時候用”三個維度,結(jié)合實際代碼案例和避坑指南,幫你徹底搞懂Java線程的創(chuàng)建方法,看完就能上手實踐。
一、繼承Thread類:最簡單的入門方式,但有局限
剛學(xué)Java時,很多人接觸的第一種線程創(chuàng)建方式就是繼承Thread類。這種方式代碼簡單,容易理解,適合新手快速上手,但實際開發(fā)中卻很少用——因為它有個致命缺點:Java是單繼承的,一旦子類繼承了Thread,就不能再繼承其他類了。
1. 具體步驟(附代碼示例)
第一步:創(chuàng)建一個子類,繼承Thread類;
第二步:重寫run()方法,把線程要執(zhí)行的任務(wù)寫在里面;
第三步:創(chuàng)建子類實例,調(diào)用start()方法啟動線程(注意:不能直接調(diào)用run()方法,否則就是普通方法調(diào)用,不會啟動新線程)。
舉個例子,我們用繼承Thread的方式實現(xiàn)一個“打印數(shù)字”的線程:
// 1. 創(chuàng)建子類繼承Thread
class NumberThread extends Thread {
private int num; // 要打印的數(shù)字上限
public NumberThread(int num) {this.num = num;
}
// 2. 重寫run()方法,定義線程任務(wù)
@Override
public void run() {
for (int i = 1; i <= num; i++) {
System.out.println(Thread.currentThread().getName() + "打印:" + i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
// 3. 創(chuàng)建實例,調(diào)用start()啟動線程
NumberThread t1 = new NumberThread(5);
t1.setName("線程A"); // 給線程起個名字,方便調(diào)試
t1.start(); // 啟動線程
// 再創(chuàng)建一個線程,打印1-3NumberThread t2 = new NumberThread(3);
t2.setName("線程B");
t2.start();
}
}
運行結(jié)果可能是(線程調(diào)度順序不確定,所以結(jié)果可能不同):
線程A打印:1
線程B打印:1
線程A打印:2
線程B打印:2
線程A打印:3
線程B打印:3
線程A打印:4
線程A打印:5
2. 為什么不推薦在實際開發(fā)中用?
除了“單繼承限制”,還有一個問題:任務(wù)與線程強耦合。run()方法里的任務(wù)邏輯和線程本身綁定在一起,無法復(fù)用——如果另一個線程想執(zhí)行同樣的任務(wù),只能再創(chuàng)建一個子類實例,不夠靈活。
所以,繼承Thread類更適合學(xué)習(xí)階段的簡單demo,實際項目中幾乎不用。
二、實現(xiàn)Runnable接口:解耦的經(jīng)典方案,推薦優(yōu)先使用
為了解決“單繼承”和“任務(wù)耦合”的問題,Java提供了Runnable接口。這種方式把“線程”和“任務(wù)”分開:線程負責(zé)調(diào)度,任務(wù)(Runnable實現(xiàn)類)負責(zé)具體邏輯,解耦性更強,也是實際開發(fā)中最常用的方式之一。
1. 具體步驟(附代碼示例)
第一步:創(chuàng)建一個類,實現(xiàn)Runnable接口;
第二步:實現(xiàn)run()方法,定義任務(wù)邏輯;
第三步:創(chuàng)建Runnable實例,作為參數(shù)傳給Thread類的構(gòu)造方法;
第四步:調(diào)用Thread實例的start()方法啟動線程。
還是用“打印數(shù)字”的例子,但這次用Runnable接口實現(xiàn):
// 1. 實現(xiàn)Runnable接口
class NumberTask implements Runnable {
private int num;
public NumberTask(int num) {this.num = num;
}
// 2. 實現(xiàn)run()方法,定義任務(wù)
@Override
public void run() {
for (int i = 1; i <= num; i++) {
// 通過Thread.currentThread()獲取當前線程
System.out.println(Thread.currentThread().getName() + "打印:" + i);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
// 3. 創(chuàng)建任務(wù)實例
NumberTask task = new NumberTask(5);
// 4. 把任務(wù)傳給Thread,啟動線程Thread t1 = new Thread(task);
t1.setName("線程C");
t1.start();
// 同一個任務(wù)可以給多個線程執(zhí)行(資源共享)
Thread t2 = new Thread(task);
t2.setName("線程D");
t2.start();
}
}
這里有個關(guān)鍵優(yōu)勢:一個Runnable任務(wù)可以被多個線程共享。比如上面的task實例被t1和t2兩個線程執(zhí)行,適合“多線程操作同一資源”的場景(比如賣票系統(tǒng),多個窗口賣同一批票)。
2. 為什么推薦用Runnable?
解耦:任務(wù)邏輯獨立于線程,同一個任務(wù)可以被多個線程復(fù)用;
支持多繼承:實現(xiàn)接口不影響類繼承其他類(比如class A extends B implements Runnable);
資源共享:多個線程共享一個Runnable實例時,能方便地操作同一批數(shù)據(jù)(需注意線程安全,比如加鎖)。
可以說,只要不是特別簡單的場景,優(yōu)先選Runnable。
三、實現(xiàn)Callable接口+FutureTask:帶返回值的進階操作
無論是Thread還是Runnable,它們的run()方法都有個問題:沒有返回值,也不能拋出受檢異常。如果線程執(zhí)行完需要返回結(jié)果(比如計算一個復(fù)雜數(shù)值),或者任務(wù)中可能拋出異常需要上層處理,這兩種方式就不夠用了。
這時,Callable接口+FutureTask組合就派上用場了——它能讓線程執(zhí)行完后返回結(jié)果,還能捕獲任務(wù)中的異常。
1. 具體步驟(附代碼示例)
第一步:創(chuàng)建一個類,實現(xiàn)Callable
第二步:實現(xiàn)call()方法(類似run(),但有返回值和異常拋出);
第三步:創(chuàng)建Callable實例,用它構(gòu)造FutureTask
第四步:把FutureTask傳給Thread,調(diào)用start()啟動線程;
第五步:通過FutureTask.get()方法獲取返回值(該方法會阻塞,直到任務(wù)執(zhí)行完)。
舉個“計算1到n的和”的例子,需要返回計算結(jié)果:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 1. 實現(xiàn)Callable接口,泛型指定返回值類型為Integer
class SumTask implements Callable
private int n;
public SumTask(int n) {this.n = n;
}
// 2. 實現(xiàn)call()方法,有返回值,可拋異常
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
Thread.sleep(100); // 模擬計算耗時
}
return sum; // 返回計算結(jié)果
}
}
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 3. 創(chuàng)建Callable實例,構(gòu)造FutureTask
SumTask task = new SumTask(100);
FutureTask
// 4. 啟動線程Thread t = new Thread(futureTask);
t.start();
// 5. 獲取返回值(會阻塞,直到任務(wù)完成)
System.out.println("計算中...");
Integer result = futureTask.get(); // 這里會等線程執(zhí)行完才繼續(xù)
System.out.println("1到100的和是:" + result); // 輸出:5050
}
}
2. 注意事項
get()方法會阻塞:如果線程還沒執(zhí)行完,調(diào)用get()會讓當前線程等待,直到任務(wù)完成。如果不想一直等,可以用get(long timeout, TimeUnit unit)設(shè)置超時時間;
異常處理:call()方法拋出的異常會被封裝到ExecutionException中,調(diào)用get()時需要捕獲;
適用場景:需要線程返回結(jié)果的場景,比如異步計算、多線程任務(wù)聚合結(jié)果等。
四、線程池:企業(yè)級開發(fā)的必選項,性能與安全的保障
前面三種方式都是“手動創(chuàng)建線程”,但在實際項目中,幾乎不會直接用new Thread()創(chuàng)建線程——因為線程是稀缺資源,頻繁創(chuàng)建/銷毀線程會消耗大量CPU和內(nèi)存,甚至可能導(dǎo)致“線程爆炸”(比如短時間創(chuàng)建幾千個線程,直接OOM)。
這時,線程池就成了“最優(yōu)解”。線程池會預(yù)先創(chuàng)建一批線程,任務(wù)來了直接復(fù)用線程,任務(wù)結(jié)束后線程不銷毀,而是放回池里等待下一個任務(wù),既能提高性能,又能控制線程數(shù)量。
1. 為什么要用線程池?
復(fù)用線程:避免頻繁創(chuàng)建/銷毀線程的開銷(線程創(chuàng)建的成本比任務(wù)執(zhí)行高得多);
控制并發(fā)數(shù):防止線程過多導(dǎo)致CPU切換頻繁(線程數(shù)超過CPU核心數(shù)時,切換線程會消耗資源);
管理任務(wù)隊列:線程池有任務(wù)隊列,當任務(wù)數(shù)超過線程數(shù)時,任務(wù)會排隊等待,避免任務(wù)丟失;
提供額外功能:比如定時任務(wù)、任務(wù)執(zhí)行超時控制、線程監(jiān)控等。
2. 如何用線程池創(chuàng)建線程?
Java通過java.util.concurrent.Executors提供了線程池工具類,常用的有:
Executors.newFixedThreadPool(n):固定線程數(shù)的線程池(n個線程,任務(wù)多了排隊);
Executors.newCachedThreadPool():可緩存線程池(線程數(shù)根據(jù)任務(wù)動態(tài)調(diào)整,空閑線程會回收);
Executors.newSingleThreadExecutor():單線程線程池(所有任務(wù)按順序執(zhí)行);
Executors.newScheduledThreadPool(n):定時任務(wù)線程池(支持延遲執(zhí)行、周期性執(zhí)行)。
但實際開發(fā)中,更推薦用ThreadPoolExecutor自定義線程池(Executors的工廠方法有潛在風(fēng)險,比如newCachedThreadPool可能創(chuàng)建無限多線程導(dǎo)致OOM)。這里先以newFixedThreadPool為例,演示線程池的使用:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 創(chuàng)建固定線程數(shù)的線程池(比如3個線程)
ExecutorService pool = Executors.newFixedThreadPool(3);
// 2. 提交任務(wù)(Runnable或Callable)for (int i = 0; i < 5; i++) { // 提交5個任務(wù)
int taskId = i;
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("線程" + Thread.currentThread().getName() + "執(zhí)行任務(wù)" + taskId);
}
});
}
// 3. 關(guān)閉線程池(不再接收新任務(wù),等現(xiàn)有任務(wù)執(zhí)行完)
pool.shutdown();
}
}
運行結(jié)果可能是(3個線程復(fù)用執(zhí)行5個任務(wù)):
線程pool-1-thread-1執(zhí)行任務(wù)0
線程pool-1-thread-2執(zhí)行任務(wù)1
線程pool-1-thread-3執(zhí)行任務(wù)2
線程pool-1-thread-1執(zhí)行任務(wù)3
線程pool-1-thread-2執(zhí)行任務(wù)4
3. 企業(yè)級開發(fā)的小提示
別用Executors的默認線程池:比如newCachedThreadPool的核心線程數(shù)是0,最大線程數(shù)是Integer.MAX_VALUE,高并發(fā)下會創(chuàng)建大量線程;
自定義ThreadPoolExecutor:通過ThreadPoolExecutor構(gòu)造方法設(shè)置核心線程數(shù)、最大線程數(shù)、隊列容量、拒絕策略等,更安全可控;
用完線程池記得關(guān)閉:調(diào)用shutdown()或shutdownNow(),避免線程池一直占用資源。
五、不同創(chuàng)建方式的對比與選擇建議
創(chuàng)建方式 | 優(yōu)點 | 缺點 | 適用場景 |
---|---|---|---|
繼承Thread類 | 代碼簡單,直接操作線程 | 單繼承限制,任務(wù)與線程耦合 | 學(xué)習(xí)demo,簡單場景 |
實現(xiàn)Runnable接口 | 解耦,支持多繼承,資源共享 | 無返回值,不能拋受檢異常 | 大多數(shù)普通任務(wù),資源共享場景 |
Callable+FutureTask | 有返回值,可拋異常 | 代碼稍復(fù)雜,get()可能阻塞 | 需要任務(wù)結(jié)果的場景(如異步計算) |
線程池 | 復(fù)用線程,控制并發(fā),性能好 | 需手動管理線程池參數(shù),關(guān)閉線程池 | 企業(yè)級開發(fā),高并發(fā)場景 |
簡單總結(jié):日常開發(fā)優(yōu)先用Runnable+線程池,需要返回值就用Callable+線程池,線程池是企業(yè)級項目的“標配”。
寫在最后
Java創(chuàng)建線程的方式雖然多,但核心邏輯都是“定義任務(wù)+啟動線程”。從入門時的Thread類,到工程化的線程池,每種方式都對應(yīng)不同的場景需求。記住:線程創(chuàng)建容易,但用好難——不僅要選對創(chuàng)建方式,還要考慮線程安全、性能優(yōu)化、資源管理。希望這篇文章能幫你理清思路,下次寫多線程代碼時,能“選得對、用得順”。
尊重原創(chuàng)文章,轉(zhuǎn)載請注明出處與鏈接:http://www.abtbt.com.cn/fangfa/736413.html,違者必究!
與“Java創(chuàng)建線程的方式-如何創(chuàng)建-有什么創(chuàng)建方法”相關(guān)文章
- java集合list的定義-ArrayList介紹-Vector介紹
- Java設(shè)計模式基本要素-要素介紹-有哪些
- Java中怎么創(chuàng)建表格-創(chuàng)建方式-創(chuàng)建操作
- java線程生命周期的狀態(tài)-生命周期介紹-有哪些
- Java實現(xiàn)線程的方式-線程怎么實現(xiàn)-實現(xiàn)方式
- Java異常處理順序-Java異常處理流程-怎么處理
- Java繼承的概念是什么-繼承的定義-實現(xiàn)方式
- Java修飾符有哪些-修飾符介紹-作用范圍
- Java常用關(guān)鍵字有哪些-有什么關(guān)鍵字-關(guān)鍵字用途
- Java代碼審計思路-代碼審計方法-怎么進行審計