如何在Java 中創(chuàng)建線程
事實上,Java 有且僅有一種方式可以真正創(chuàng)建出線程,那就是通過調(diào)用 Thread 類中的 start() 方法。因為只有這個方法會去真正調(diào)用 JVM 本地方法,進而進行操作系統(tǒng)創(chuàng)建線程的系統(tǒng)調(diào)用,并讓 Java 線程與操作系統(tǒng)線程產(chǎn)生映射關(guān)系,最后將實現(xiàn)了 Runnable 接口的任務(wù)傳遞給線程。只要線程獲得了 CPU 時間片,就進入 run() 方法執(zhí)行具體的代碼邏輯。
? 所以,僅僅有 start 方法我們就可以創(chuàng)建一個線程了,如下面的代碼所示。只不過這個線程什么都不會做,它是一個空線程,因為我們沒有以任何方式提供給其 “可執(zhí)行任務(wù)”。
public class Demo1 {
public static void main(String[] args) {
Thread thread = new Thread();
System.out.println(thread.getName()); // Thread-0
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE
}
}
? 在下面介紹的所謂創(chuàng)建線程的方式,其實就是在介紹為線程提供 “可執(zhí)行任務(wù)” 的方式,這些方式最終都會調(diào)用到 Thread 類中的 start() 方法。
1. 繼承 Thread 類
? 這種方式是通過重寫 Thread 類中的 run() 方法來為線程提供可執(zhí)行任務(wù)。
class MyThread extends Thread {
public void run() {
System.out.println(currentThread().getName());
}
}
public class Demo2 {
public static void main(String[] args) {
Thread myThread1 = new MyThread();
Thread myThread2 = new MyThread();
myThread1.start(); // Thread-0
myThread2.start(); // Thread-1
}
}? 這種方式一般是不推薦使用的,因為它在邏輯上不能將 ”線程“ 的概念和 ”線程任務(wù)“ 的概念很好地分離開。假設(shè)現(xiàn)在我有一個數(shù)字,我想要使用兩個線程同時對這個數(shù)字進行增加,直至這個數(shù)字變?yōu)?100。如果要通過繼承 Thread 類實現(xiàn),這個數(shù)字就只能作為 Thread 子類的一個靜態(tài)屬性存在,只有這樣這個變量才能在類實例之間共享。但是,這樣做就會導(dǎo)致代碼可讀性和可維護性都很差,因為自增數(shù)字是一個任務(wù),現(xiàn)在它被耦合在線程中。因此我們大部分情況還是要使用接下來介紹的方式。
? 不過,通過繼承 Thread 類實現(xiàn)共享數(shù)字自增的代碼我也放在下面,感興趣的可以看一下,這里使用 CAS 保證線程安全,不懂的話可以先略過這塊。
class MyThread extends Thread {
private final static AtomicInteger goodsAmount = new AtomicInteger(0);
public void run() {
while (true) {
// 使用 current 作為線程內(nèi)部的臨時變量,相當于快照,無線程安全問題
int current = goodsAmount.get();
if (current >= 200) {
break;
}
// 當且僅當 goodsAmount 的值沒有被其他線程修改過時自增,
// 如果被修改過那它的值肯定不是剛剛記錄過的 current 了。
if (goodsAmount.compareAndSet(current, current + 1)) {
// 每個線程只會在成功 CAS 的時候打印一次,并且打印的是增加后的值。
// 所以這里的日志應(yīng)該嚴格打印 200 次
System.out.println(currentThread().getName() + ":" + (current + 1));
}
}
}
}
public class Demo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread myThread1 = new MyThread();
Thread myThread2 = new MyThread();
Thread myThread3 = new MyThread();
myThread1.start();
myThread2.start();
myThread3.start();
myThread1.join();
myThread2.join();
myThread3.join();
}
}2. 實現(xiàn) Runnable 接口
? 我們先來觀察 Thread 類中 run() 方法的源碼。如果我們不選擇重寫它,那么可以看出這個方法主要進行了一個邏輯判斷,如果 task 為空就什么都不做,如果 task 不為空就調(diào)用 runWith 真正執(zhí)行該任務(wù)。那么 task 什么時候不為空呢,就是當我們在 Thread 的構(gòu)造器中傳入任務(wù)的時候,這才引出了下面的幾種方式。
@Override
public void run() {
Runnable task = holder.task;
if (task != null) {
Object bindings = scopedValueBindings();
runWith(bindings, task);
}
}
? 如下展示了三種通過實現(xiàn) Runnable 接口來為線程傳遞可執(zhí)行任務(wù)的方式:
class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Demo2 {
public static void main(String[] args) {
new Thread(new Task()).start(); // Thread-2
new Thread(new Runnable() { // 使用匿名內(nèi)部類
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start(); // Thread-3
new Thread(() -> { // 使用 lambda 表達式
System.out.println(Thread.currentThread().getName());
}).start(); // Thread-4
}
}3. 獲取異步執(zhí)行結(jié)果
? 上面的兩種方式都不能獲取異步執(zhí)行結(jié)果,因為 run() 方法返回 void,這表示我們沒有管控線程執(zhí)行任務(wù)的能力:我們無法中途停止任務(wù),也無法獲取任務(wù)的執(zhí)行狀態(tài),更無法獲取任務(wù)執(zhí)行結(jié)果。
? 在 Java 中,獲取異步任務(wù)執(zhí)行信息的功能由 Future 接口進行規(guī)范,而一個可執(zhí)行任務(wù)由 Runnable 接口進行規(guī)范。這意味著,只有同時實現(xiàn)這兩個接口的類才可以作為一個 “可獲取異步執(zhí)行結(jié)果的可執(zhí)行任務(wù)” 傳入 Thread 的構(gòu)造器。
? 這個類就是 FutureTask,它真正實現(xiàn)了上述功能,這依賴于它的兩個關(guān)鍵屬性 Callable<V> callable 和 Object outcome。前者是我們傳入的帶返回值的異步任務(wù);后者是 FutureTask 保存的任務(wù)執(zhí)行后的返回值。FutureTask 提供了 get() 方法供我們?nèi)〕?outcome,同時還提供了取消任務(wù)、判斷任務(wù)是否執(zhí)行完、判斷任務(wù)是否被取消等獲取異步任務(wù)執(zhí)行信息的功能。

所以,現(xiàn)在的邏輯變成了:我們需要先讓自己的任務(wù)實現(xiàn) Callable 接口,這表示我們的任務(wù)是一個經(jīng) Callable 規(guī)范的、有返回值的任務(wù)。接下來,我們還要把任務(wù)包裝在 FutureTask 中,傳入 Thread 的構(gòu)造器。Thread 不管你提供的到底是什么任務(wù),只要實現(xiàn)了 Runnable 即可,因為這規(guī)范了你傳入任務(wù)一定會有一個 run() 方法。Thread 先調(diào)用自己的 run() 方法,在內(nèi)部調(diào)用到 FutureTask 的 run() 方法,F(xiàn)utureTask 的 run() 方法又會調(diào)用到我們最初實現(xiàn) Callable 接口時實現(xiàn)的 call() 方法,這里才是我們真正定義的代碼邏輯。執(zhí)行完 call() 后,F(xiàn)utureTask 將返回值保存在 outcome 屬性中,供我們?nèi)〕觥?/p>
class CallableTask implements Callable<String> {
@Override
public String call() {
System.out.println(Thread.currentThread().getName());
return "任務(wù)執(zhí)行成功!";
}
}
public class Demo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new CallableTask());
new Thread(futureTask).start();
System.out.println(futureTask.get()); // 任務(wù)執(zhí)行成功!
}
}到此這篇關(guān)于在 Java 中創(chuàng)建線程的文章就介紹到這了,更多相關(guān)java創(chuàng)建線程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解使用@RequestBody取POST方式的json字符串
這篇文章主要介紹了詳解使用@RequestBody取POST方式的json字符串,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-12-12
Spring框架基于注解的AOP之各種通知的使用與環(huán)繞通知實現(xiàn)詳解
這篇文章主要介紹了Spring框架基于注解的AOP之各種通知的使用及其環(huán)繞通知,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-11-11
MyBatisPlus中@TableField注解的基本使用
這篇文章主要介紹了MyBatisPlus中@TableField注解的基本使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07

