Java 多線(xiàn)程
本小節(jié)我們將學(xué)習(xí) Java 多線(xiàn)程,通過(guò)本小節(jié)的學(xué)習(xí),你將了解到什么是線(xiàn)程,如何創(chuàng)建線(xiàn)程,創(chuàng)建線(xiàn)程有哪幾種方式,線(xiàn)程的狀態(tài)、生命周期等內(nèi)容。掌握多線(xiàn)程的代碼編寫(xiě),并理解線(xiàn)程生命周期等內(nèi)容是本小節(jié)學(xué)習(xí)的重點(diǎn)。
1. 什么是線(xiàn)程
要了解什么是線(xiàn)程,就要先了解進(jìn)程的概念。
進(jìn)程,是指計(jì)算機(jī)中已運(yùn)行的程序,它是一個(gè)動(dòng)態(tài)執(zhí)行的過(guò)程。假設(shè)我們電腦上同時(shí)運(yùn)行了瀏覽器、QQ 以及代碼編輯器三個(gè)軟件,這三個(gè)軟件之所以同時(shí)運(yùn)行,就是進(jìn)程所起的作用。
線(xiàn)程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。大部分情況下,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。也就是說(shuō)一個(gè)進(jìn)程可以包含多個(gè)線(xiàn)程, 因此線(xiàn)程也被稱(chēng)為輕量級(jí)進(jìn)程。
如果你還是對(duì)于進(jìn)程和線(xiàn)程的概念有所困惑,推薦一篇比較優(yōu)秀的文章,有助于幫助你理解進(jìn)程和線(xiàn)程的概念。
2. 創(chuàng)建線(xiàn)程
在 Java 中,創(chuàng)建線(xiàn)程有以下 3 種方式:
- 繼承
Thread
類(lèi),重寫(xiě)run()
方法,該方法代表線(xiàn)程要執(zhí)行的任務(wù); - 實(shí)現(xiàn)
Runnable
接口,實(shí)現(xiàn)run()
方法,該方法代表線(xiàn)程要執(zhí)行的任務(wù); - 實(shí)現(xiàn)
Callable
接口,實(shí)現(xiàn)call()
方法,call()
方法作為線(xiàn)程的執(zhí)行體,具有返回值,并且可以對(duì)異常進(jìn)行聲明和拋出。
下面我們分別來(lái)看下這 3 種方法的具體實(shí)現(xiàn)。
2.1 Thread 類(lèi)
Thread
類(lèi)是一個(gè)線(xiàn)程類(lèi),位于 java.lang
包下。
2.1.1 構(gòu)造方法
Thread
類(lèi)的常用構(gòu)造方法如下:
Thread()
:創(chuàng)建一個(gè)線(xiàn)程對(duì)象;Thread(String name)
:創(chuàng)建一個(gè)指定名稱(chēng)的線(xiàn)程對(duì)象;Thread(Runnable target)
:創(chuàng)建一個(gè)基于Runnable
接口實(shí)現(xiàn)類(lèi)的線(xiàn)程對(duì)象;Thread(Runnable target, String name)
:創(chuàng)建一個(gè)基于Runnable
接口實(shí)現(xiàn)類(lèi),并具有指定名稱(chēng)的線(xiàn)程對(duì)象。
2.1.2 常用方法
void run()
:線(xiàn)程相關(guān)的代碼寫(xiě)在該方法中,一般需要重寫(xiě);
void start()
:?jiǎn)?dòng)當(dāng)前線(xiàn)程;
static void sleep(long m)
:使當(dāng)前線(xiàn)程休眠 m
毫秒;
void join()
:優(yōu)先執(zhí)行調(diào)用 join()
方法的線(xiàn)程。
Tips:
run()
方法是一個(gè)非常重要的方法,它是用于編寫(xiě)線(xiàn)程執(zhí)行體的方法,不同線(xiàn)程之間的一個(gè)最主要區(qū)別就是run()
方法中的代碼是不同的。
可翻閱官方文檔以查看更多 API。
2.1.3 實(shí)例
通過(guò)繼承 Thread
類(lèi)創(chuàng)建線(xiàn)程可分為以下 3 步:
- 定義
Thread
類(lèi)的子類(lèi),并重寫(xiě)該類(lèi)的run()
方法。run()
方法的方法體就代表了線(xiàn)程要完成的任務(wù); - 創(chuàng)建
Thread
子類(lèi)的實(shí)例,即創(chuàng)建線(xiàn)程對(duì)象; - 調(diào)用線(xiàn)程對(duì)象的
start
方法來(lái)啟動(dòng)該線(xiàn)程。
具體實(shí)例如下:
/**
* @author colorful@TaleLin
*/
public class ThreadDemo1 extends Thread {
/**
* 重寫(xiě) Thread() 的方法
*/
@Override
public void run() {
System.out.println("這里是線(xiàn)程體");
// 當(dāng)前打印線(xiàn)程的名稱(chēng)
System.out.println(getName());
}
public static void main(String[] args) {
// 實(shí)例化 ThreadDemo1 對(duì)象
ThreadDemo1 threadDemo1 = new ThreadDemo1();
// 調(diào)用 start() 方法,以啟動(dòng)線(xiàn)程
threadDemo1.start();
}
}
運(yùn)行結(jié)果:
這里是線(xiàn)程體
Thread-0
小伙伴們可能會(huì)有疑問(wèn),上面這樣的代碼,和普通的類(lèi)實(shí)例化以及方法調(diào)用有什么區(qū)別的,下面我們來(lái)看一個(gè)稍微復(fù)雜些的實(shí)例:
/**
* @author colorful@TaleLin
*/
public class ThreadDemo2 {
/**
* 靜態(tài)內(nèi)部類(lèi)
*/
static class MyThread extends Thread {
private int i = 3;
MyThread(String name) {
super(name);
}
@Override
public void run() {
while (i > 0) {
System.out.println(getName() + " i = " + i);
i--;
}
}
}
public static void main(String[] args) {
// 創(chuàng)建兩個(gè)線(xiàn)程對(duì)象
MyThread thread1 = new MyThread("線(xiàn)程1");
MyThread thread2 = new MyThread("線(xiàn)程2");
// 啟動(dòng)線(xiàn)程
thread1.start();
thread2.start();
}
}
運(yùn)行結(jié)果:
線(xiàn)程2 i = 3
線(xiàn)程1 i = 3
線(xiàn)程1 i = 2
線(xiàn)程2 i = 2
線(xiàn)程1 i = 1
線(xiàn)程2 i = 1
代碼中我們是先啟動(dòng)了線(xiàn)程 1,再啟動(dòng)了線(xiàn)程 2 的,觀(guān)察運(yùn)行結(jié)果,線(xiàn)程并不是按照我們所預(yù)想的順序執(zhí)行的。這里就要?jiǎng)澲攸c(diǎn)了,不同線(xiàn)程,執(zhí)行順序是隨機(jī)的。如果你再執(zhí)行幾次代碼,可以觀(guān)察到每次的運(yùn)行結(jié)果都可能不同:

2.2 Runnable 接口
2.2.1 為什么需要 Runnable
接口
通過(guò)實(shí)現(xiàn) Runnable
接口的方案來(lái)創(chuàng)建線(xiàn)程,要優(yōu)于繼承 Thread
類(lèi)的方案,主要有以下原因:
- Java 不支持多繼承,所有的類(lèi)都只允許繼承一個(gè)父類(lèi),但可以實(shí)現(xiàn)多個(gè)接口。如果繼承了
Thread
類(lèi)就無(wú)法繼承其它類(lèi),這不利于擴(kuò)展; - 繼承
Thread
類(lèi)通常只重寫(xiě)run()
方法,其他方法一般不會(huì)重寫(xiě)。繼承整個(gè)Thread
類(lèi)成本過(guò)高,開(kāi)銷(xiāo)過(guò)大。
2.2.2 實(shí)例
通過(guò)實(shí)現(xiàn) Runnable
接口創(chuàng)建線(xiàn)程的步驟如下:
- 定義
Runnable
接口的實(shí)現(xiàn)類(lèi),并實(shí)現(xiàn)該接口的run()
方法。這個(gè)run()
方法的方法體同樣是該線(xiàn)程的線(xiàn)程執(zhí)行體; - 創(chuàng)建
Runnable
實(shí)現(xiàn)類(lèi)的實(shí)例,并以此實(shí)例作為Thread
的target
來(lái)創(chuàng)建Thread
對(duì)象,該Thread
對(duì)象才是真正的線(xiàn)程對(duì)象; - 調(diào)用線(xiàn)程對(duì)象的
start
方法來(lái)啟動(dòng)該線(xiàn)程。
具體實(shí)例如下:
/**
* @author colorful@TaleLin
*/
public class RunnableDemo1 implements Runnable {
private int i = 5;
@Override
public void run() {
while (i > 0) {
System.out.println(Thread.currentThread().getName() + " i = " + i);
i--;
}
}
public static void main(String[] args) {
// 創(chuàng)建兩個(gè)實(shí)現(xiàn) Runnable 實(shí)現(xiàn)類(lèi)的實(shí)例
RunnableDemo1 runnableDemo1 = new RunnableDemo1();
RunnableDemo1 runnableDemo2 = new RunnableDemo1();
// 創(chuàng)建兩個(gè)線(xiàn)程對(duì)象
Thread thread1 = new Thread(runnableDemo1, "線(xiàn)程1");
Thread thread2 = new Thread(runnableDemo2, "線(xiàn)程2");
// 啟動(dòng)線(xiàn)程
thread1.start();
thread2.start();
}
}
運(yùn)行結(jié)果:
線(xiàn)程1 i = 5
線(xiàn)程1 i = 4
線(xiàn)程1 i = 3
線(xiàn)程1 i = 2
線(xiàn)程2 i = 5
線(xiàn)程1 i = 1
線(xiàn)程2 i = 4
線(xiàn)程2 i = 3
線(xiàn)程2 i = 2
線(xiàn)程2 i = 1
2.3 Callable 接口
2.3.1 為什么需要 Callable
接口
繼承 Thread 類(lèi)和實(shí)現(xiàn) Runnable 接口這兩種創(chuàng)建線(xiàn)程的方式都沒(méi)有返回值。所以,線(xiàn)程執(zhí)行完畢后,無(wú)法得到執(zhí)行結(jié)果。為了解決這個(gè)問(wèn)題,Java 5 后,提供了 Callable
接口和 Future
接口,通過(guò)它們,可以在線(xiàn)程執(zhí)行結(jié)束后,返回執(zhí)行結(jié)果。
2.3.2 實(shí)例
通過(guò)實(shí)現(xiàn) Callable
接口創(chuàng)建線(xiàn)程步驟如下:
- 創(chuàng)建
Callable
接口的實(shí)現(xiàn)類(lèi),并實(shí)現(xiàn)call()
方法。這個(gè)call()
方法將作為線(xiàn)程執(zhí)行體,并且有返回值; - 創(chuàng)建
Callable
實(shí)現(xiàn)類(lèi)的實(shí)例,使用FutureTask
類(lèi)來(lái)包裝Callable
對(duì)象,這個(gè)FutureTask
對(duì)象封裝了該Callable
對(duì)象的call()
方法的返回值; - 使用
FutureTask
對(duì)象作為Thread
對(duì)象的 target 創(chuàng)建并啟動(dòng)新線(xiàn)程; - 調(diào)用
FutureTask
對(duì)象的get()
方法來(lái)獲得線(xiàn)程執(zhí)行結(jié)束后的返回值。
具體實(shí)例如下:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author colorful@TaleLin
*/
public class CallableDemo1 {
static class MyThread implements Callable<String> {
@Override
public String call() { // 方法返回值類(lèi)型是一個(gè)泛型,在上面 Callable<String> 處定義
return "我是線(xiàn)程中返回的字符串";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 常見(jiàn)實(shí)現(xiàn)類(lèi)的實(shí)例
Callable<String> callable = new MyThread();
// 使用 FutureTask 類(lèi)來(lái)包裝 Callable 對(duì)象
FutureTask<String> futureTask = new FutureTask<>(callable);
// 創(chuàng)建 Thread 對(duì)象
Thread thread = new Thread(futureTask);
// 啟動(dòng)線(xiàn)程
thread.start();
// 調(diào)用 FutureTask 對(duì)象的 get() 方法來(lái)獲得線(xiàn)程執(zhí)行結(jié)束后的返回值
String s = futureTask.get();
System.out.println(s);
}
}
運(yùn)行結(jié)果:
我是線(xiàn)程中返回的字符串
3. 線(xiàn)程休眠
在前面介紹 Thread
類(lèi)的常用方法時(shí),我們介紹了 sleep()
靜態(tài)方法,該方法可以使當(dāng)前執(zhí)行的線(xiàn)程睡眠(暫時(shí)停止執(zhí)行)指定的毫秒數(shù)。
線(xiàn)程休眠的實(shí)例如下:
/**
* @author colorful@TaleLin
*/
public class SleepDemo implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 5; i ++) {
// 打印語(yǔ)句
System.out.println(Thread.currentThread().getName() + ":執(zhí)行第" + i + "次");
try {
// 使當(dāng)前線(xiàn)程休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 實(shí)例化 Runnable 的實(shí)現(xiàn)類(lèi)
SleepDemo sleepDemo = new SleepDemo();
// 實(shí)例化線(xiàn)程對(duì)象
Thread thread = new Thread(sleepDemo);
// 啟動(dòng)線(xiàn)程
thread.start();
}
}
運(yùn)行結(jié)果:
Thread-0:執(zhí)行第1次
Thread-0:執(zhí)行第2次
Thread-0:執(zhí)行第3次
Thread-0:執(zhí)行第4次
Thread-0:執(zhí)行第5次

4. 線(xiàn)程的狀態(tài)和生命周期
java.lang.Thread.Starte
枚舉類(lèi)中定義了 6 種不同的線(xiàn)程狀態(tài):
NEW
:新建狀態(tài),尚未啟動(dòng)的線(xiàn)程處于此狀態(tài);RUNNABLE
:可運(yùn)行狀態(tài),Java 虛擬機(jī)中執(zhí)行的線(xiàn)程處于此狀態(tài);BLOCK
:阻塞狀態(tài),等待監(jiān)視器鎖定而被阻塞的線(xiàn)程處于此狀態(tài);WAITING
:等待狀態(tài),無(wú)限期等待另一線(xiàn)程執(zhí)行特定操作的線(xiàn)程處于此狀態(tài);TIME_WAITING
:定時(shí)等待狀態(tài),在指定等待時(shí)間內(nèi)等待另一線(xiàn)程執(zhí)行操作的線(xiàn)程處于此狀態(tài);TERMINATED
:結(jié)束狀態(tài),已退出的線(xiàn)程處于此狀態(tài)。
值得注意的是,一個(gè)線(xiàn)程在給定的時(shí)間點(diǎn)只能處于一種狀態(tài)。這些狀態(tài)是不反映任何操作系統(tǒng)線(xiàn)程狀態(tài)的虛擬機(jī)狀態(tài)。
線(xiàn)程的生命周期,實(shí)際上就是上述 6 個(gè)線(xiàn)程狀態(tài)的轉(zhuǎn)換過(guò)程。下圖展示了一個(gè)完整的生命周期:

5. 小結(jié)
通過(guò)本小節(jié)的學(xué)習(xí),我們知道了線(xiàn)程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。線(xiàn)程也被稱(chēng)為輕量級(jí)進(jìn)程。在 Java 中,可以以 3 種方式創(chuàng)建線(xiàn)程,分別是繼承 Thread
類(lèi)、實(shí)現(xiàn) Runnable
接口以及實(shí)現(xiàn) Callable
接口??梢允褂渺o態(tài)方法 sleep()
讓線(xiàn)程休眠。線(xiàn)程狀態(tài)有 6 種,也有資料上說(shuō)線(xiàn)程有 5 種,這部分內(nèi)容我們按照 Java 源碼中的定義 6 種來(lái)記憶即可。