實(shí)際應(yīng)用中,多線程非常有用,例如,QQ音樂就是一個多線程程序,我們可以一邊聽音樂,一般下載音樂,還可以同時播放MV等非常方便。一個Web服務(wù)器通過多線程同時處理多個請求,比如Tomcat就是多線程的。
前言
前段時間推出的JAVA8新特性文章收到大家廣泛關(guān)注和好評,非常感謝各位支持,這段時間苦思冥想,決定輸出一波Java多線程技能點(diǎn),希望可以在大家的工作和面試中有所幫助!本篇文章為多線程系列第一章,主要講解一下幾點(diǎn):
多線程好處和應(yīng)用場景
多線程的相關(guān)概念和術(shù)語
Java線程創(chuàng)建方式
Thread類詳解,線程的常用方法
線程5種狀態(tài)和6種狀態(tài),兩種版本解釋
線程狀態(tài)之間轉(zhuǎn)換
Java設(shè)計(jì)者寫過一個很有影響力的白皮書,用來解釋設(shè)計(jì)的初衷,并發(fā)布了一個簡短的摘要,分為11個術(shù)語:
- 簡單性
- 面向?qū)ο?/li>
- 分布式
- 健壯性
- 安全性
- 體系結(jié)構(gòu)中立
- 可移植性
- 解釋型
- 高性能
- 多線程
- 動態(tài)性
其中多線程就是本次要接觸的,白皮書中對多線程的解釋:
多線程可以帶來更好的交互響應(yīng)和實(shí)時行為。
如今,我們非常關(guān)注并發(fā)性,因?yàn)槟柖尚袑⑼杲Y(jié)。我們不再追求更快的處理器,而是著眼于獲得更多的處理器,而且要讓它們一直保持工作。不過,可以看到,大多數(shù)編程語言對于這個問題并沒有顯示出足夠的重視。Java在當(dāng)時很超前。它是第一個支持并發(fā)程序設(shè)計(jì)的主流語言。從白皮書中可以看到,它的出發(fā)點(diǎn)稍有些不同。當(dāng)時,多核處理器還很神秘,而Web編程才剛剛起步,處理器要花很長時間等待服務(wù)器響應(yīng),需要并發(fā)程序設(shè)計(jì)來確保用戶界面不會"凍住"。并發(fā)程序設(shè)計(jì)絕非易事,不過Java在這方面表現(xiàn)很出色,可以很好地管理這個工作。
在操作系統(tǒng)中有多任務(wù)【multitasking】,在同一刻運(yùn)行多個程序【應(yīng)用】的能力。例如,在聽音樂的同時可以邊打游戲,邊寫代碼。如今我們的電腦大多都是多核CPU,但是,并發(fā)執(zhí)行的進(jìn)程【正在執(zhí)行的應(yīng)用】數(shù)目并不是由CPU數(shù)目制約的。操作系統(tǒng)將CPU的時間片分配給每一個進(jìn)程,給人并行處理的感覺。
相關(guān)概念
程序【program】:為了完成特定任務(wù),用某種語言編寫的一組指令的集合。程序就是一堆代碼,一組數(shù)據(jù)和指令集,是一個靜態(tài)的概念。就說我們程序員寫的那玩意。比如:安裝在電腦或者手機(jī)上的各種軟件,今日頭條、抖音、懂車帝等,如果一個程序支持多線程,這個程序就是一個多線程程序
進(jìn)程【Process】:是程序的一次執(zhí)行過程或者說是正在運(yùn)行的程序,是一個動態(tài)概念,進(jìn)程存在生命周期,也就是說程序隨著程序的終止而銷毀
線程【Thread】:線程是進(jìn)程中的實(shí)際運(yùn)作的單位,是進(jìn)程的一條流水線,是程序的實(shí)際執(zhí)行者,是最小的執(zhí)行單位。通常在一個進(jìn)程中可以包含若干個線程,當(dāng)然一個進(jìn)程中至少有一個線程。線程是CPU調(diào)度和執(zhí)行的最小單位
CPU時間片:時間片即CPU分配給各個程序的時間,每個進(jìn)程被分配一個時間段,稱作它的時間片,即該進(jìn)程允許運(yùn)行的時間,使各個程序從表面上看是同時進(jìn)行的,如果在時間片結(jié)束時進(jìn)程還在運(yùn)行,則CPU將被剝奪并分配給另一個進(jìn)程。如果進(jìn)程在時間片結(jié)束前阻塞或結(jié)束,則CPU當(dāng)即進(jìn)行切換。而不會造成CPU資源浪費(fèi)
并行【parallel】:多個任務(wù)同時進(jìn)行,并行必須有多核才能實(shí)現(xiàn),否則只能是并發(fā),比如:多名學(xué)生有問題,同時有多名老師可以輔導(dǎo)解決
串行【serial】:一個程序處理完當(dāng)前進(jìn)程,按照順序接著處理下一個進(jìn)程,一個接著一個進(jìn)行,比如:多名學(xué)生有問題,只有一名老師,需要挨個解決
并發(fā)【concurrency】:同一個對象被多個線程同時操作。(這是一種假并行。即一個CPU的情況下,在同一個時間點(diǎn),CPU只能執(zhí)行一個代碼,因?yàn)榍袚Q的很快,所以就有同時執(zhí)行的錯覺),比如:多名學(xué)生有問題,只有一個老師,他一會處理A同學(xué),一會處理B同學(xué),一會處理C同學(xué),頻繁切換,看起來好似在同時處理學(xué)生問題
多線程意義
實(shí)際應(yīng)用中,多線程非常有用,例如,QQ音樂就是一個多線程程序,我們可以一邊聽音樂,一般下載音樂,還可以同時播放MV等非常方便。一個Web服務(wù)器通過多線程同時處理多個請求,比如Tomcat就是多線程的。
注意:程序會因?yàn)橐攵嗑€程而變的復(fù)雜,多線程同時會帶來一些問題,需要我們解決
多線程應(yīng)用場景
- 程序需要同時執(zhí)行兩個或多個任務(wù)。
- 程序需要實(shí)現(xiàn)一些需要等待的任務(wù)時,如用戶輸入、文件讀寫操作、網(wǎng)絡(luò)操作、搜索等。
- 需要一些后臺運(yùn)行的程序時。
多線程多數(shù)在瀏覽器、Web服務(wù)器、數(shù)據(jù)庫、各種專用服務(wù)器【如游戲服務(wù)器】、分布式計(jì)算等場景出現(xiàn)。
在使用Java編寫后臺服務(wù)時,如果遇到并發(fā)較高、需要后臺任務(wù)、需要長時間處理大數(shù)據(jù)等情況都可以創(chuàng)建線程單獨(dú)的線程處理這些事項(xiàng),多線程的目的就在于提高處理速度,減少用戶等待時間
- 后臺記錄日志,創(chuàng)建額外線程來記錄日志
- 大量用戶請求,創(chuàng)建多個線程共同處理請求
- 下載大文件,可以創(chuàng)建單獨(dú)的線程,不影響正常的業(yè)務(wù)流暢度
- ......
多線程創(chuàng)建方式
線程創(chuàng)建有4種方式:
方式1:繼承Thread類
方式2:實(shí)現(xiàn)Runnable接口
方式3:實(shí)現(xiàn)Callable接口
方式4:使用線程池【這塊后邊單獨(dú)說,它更像是管理線程的手段】
繼承Thread
步驟:
- 自定義類繼承Thread類
- 重寫run方法,run方法就是線程的功能執(zhí)行體
- 創(chuàng)建線程對象,調(diào)用start方法啟動線程
- 啟動線程之后,線程不一定會立即執(zhí)行,需要得到CPU分配的時間片,也就是拿到CPU執(zhí)行權(quán)限才會執(zhí)行
JDK源碼中,Thread類定義實(shí)現(xiàn)了Runnable接口

所以知道重寫的run方法從哪來的了吧!就是從Runnable接口中來的
需求:創(chuàng)建線程計(jì)算10以內(nèi)的偶數(shù)
線程類:
public class ThreadTest extends Thread{
// run方法是 線程體,啟動線程時會運(yùn)行run()方法中的代碼
@Override
public void run() {
// 輸出10以內(nèi)偶數(shù)
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
測試類:
測試類中輸出了一句話:主線程
public class ThreadMain {
public static void main(String[] args) {
// 1、創(chuàng)建線程對象
ThreadTest t1 = new ThreadTest();
// 2、調(diào)用start方法啟動線程
t1.start();
System.out.println("主線程");
}
}
打印結(jié)果:

實(shí)現(xiàn)Runnable接口
步驟:
- 自定義類實(shí)現(xiàn)Runnable接口
- 實(shí)現(xiàn)run方法
- 創(chuàng)建實(shí)現(xiàn)類對象
- 創(chuàng)建Thread對象,在構(gòu)造方法中傳入實(shí)現(xiàn)類對象作為參數(shù)
- 調(diào)用Thread對象的start方法啟動線程
同樣的需求打印10以內(nèi)的偶數(shù)
實(shí)現(xiàn)類:
public class RunnableImpl implements Runnable{
@Override
public void run() {
// 輸出10以內(nèi)偶數(shù)
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創(chuàng)建實(shí)現(xiàn)類對象
RunnableImpl runnable = new RunnableImpl();
// 2、創(chuàng)建線程對象,接收實(shí)現(xiàn)類,因?yàn)閷?shí)現(xiàn)類中的run方法承載了線程的功能
Thread t1 = new Thread(runnable);
// 3、啟動線程
t1.start();
// 主線程
System.out.println("主線程");
}
}
Callable接口
FutureTask類:

RunnableFuture接口:

步驟:
- 新建類實(shí)現(xiàn)Callable接口,并指定泛型類型,類型就是線程計(jì)算之后的結(jié)果的類型
- 實(shí)現(xiàn)call方法,call方法跟run方法類似,不過該方法有返回值和異常拋出,都是來封裝線程功能體的
- 在測試類中創(chuàng)建實(shí)現(xiàn)類對象,并且創(chuàng)建 FutureTask 對象將實(shí)現(xiàn)類對象當(dāng)做構(gòu)造方法參數(shù)傳入
- 創(chuàng)建Thread線程對象,將 FutureTask 對象當(dāng)做構(gòu)造方法參數(shù)傳入,并調(diào)用start方法啟動線程
- 可以通過 FutureTask 對象的get方法獲取線程的運(yùn)算結(jié)果
案例:還是計(jì)算10以內(nèi)的偶數(shù),這一次將計(jì)算結(jié)果返回,因?yàn)橛卸鄠€數(shù)據(jù)所以返回?cái)?shù)據(jù)用集合存儲,則Callable接口的泛型類型應(yīng)該是集合
實(shí)現(xiàn)類:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
// 1、實(shí)現(xiàn)Callable,指明泛型類型
public class CallableImpl implements Callable<List<Integer>> {
// 2、線程返回Integer類型數(shù)據(jù),拋出異常
@Override
public List<Integer> call() throws Exception {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
// 3、偶數(shù)存儲到集合中
list.add(i);
}
}
// 4、返回集合
return list;
}
}
測試類:
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableMain {
public static void main(String[] args) {
// 1、創(chuàng)建Callable實(shí)現(xiàn)類對象
CallableImpl callable = new CallableImpl();
// 2、創(chuàng)建 FutureTask對象傳入 callable
// FutureTask 實(shí)現(xiàn) 了 RunnableFuture,RunnableFuture實(shí)現(xiàn)了Runnable接口和Future接口
FutureTask<List<Integer>> task = new FutureTask<>(callable);
// 3、將 task 傳入線程對象
Thread t1 = new Thread(task);
// 4、啟動線程
t1.start();
// 5、獲取線程返回?cái)?shù)據(jù)
try {
List<Integer> list = task.get();
System.out.println(list);
} catch (Exception e) {
e.printStackTrace();
}
}
}
三種實(shí)現(xiàn)方式區(qū)別
- Java單繼承的特性決定,使用實(shí)現(xiàn)接口的方式創(chuàng)建線程更靈活
- Callable實(shí)現(xiàn)call方法有返回值和異常拋出,方便定位問題,配合FutureTask可以獲取線程運(yùn)算結(jié)果,而run方法沒有返回值,沒有異常拋出
- 如果線程運(yùn)行結(jié)束后不需要返回值,則推薦使用實(shí)現(xiàn)Runnable接口方式
小貼士:有不少文章中寫到【實(shí)現(xiàn)的方式更適合用來處理多個線程有共享數(shù)據(jù)的情況】,很多小伙伴也拿去背,這句話怎么看都不對吧,多線程共享數(shù)據(jù)不加鎖,不同步怎么著也不能避免線程安全問題!
線程開辟
- 開辟線程需要通過Thread類創(chuàng)建對象
- 啟動線程需要Thread對象調(diào)用start方法
- 線程的功能可以裝在Thread類的run方法或者Runnable接口實(shí)現(xiàn)類的run方法類中
- 線程開辟需要在另一個線程中開啟,新開辟的線程稱為子線程

小貼士:對于Java應(yīng)用程序java.exe來講,至少會存在三個線程:
main主線程
gc垃圾回收線程
異常處理線程,如果發(fā)生異常會影響主線程。
線程狀態(tài)
線程的狀態(tài)網(wǎng)上有 5種狀態(tài) 和 6種狀態(tài) 兩個版本
五種狀態(tài)版本:是基于現(xiàn)代操作系統(tǒng)線程狀態(tài)角度解釋的
新建:當(dāng)一個Thread類或其子類的對象被聲明并創(chuàng)建時,新生的線程對象處于新建狀態(tài)
就緒:處于新建狀態(tài)的線程被start后,將進(jìn)入線程隊(duì)列等待CPU時間片,此時它已具備了運(yùn)行的條件,只是沒分配到CPU資源
運(yùn)行:當(dāng)就緒的線程被調(diào)度并獲得CPU資源時,便進(jìn)入運(yùn)行狀態(tài),run方法定義了線程的操作和功能
阻塞:在某種特殊情況下,被人為掛起或執(zhí)行輸入輸出操作時,讓出CPU并臨時終止自己的執(zhí)行,進(jìn)入阻塞狀態(tài)
死亡:線程完成了它的全部工作或線程被提前強(qiáng)制性地中止或出現(xiàn)異常導(dǎo)致結(jié)束
在JDK5的時候Thread類中定義了一個State枚舉類,其中定義了6種線程狀態(tài),這是Java官方定義的Java線程的6種狀態(tài)

1)NEW:處于NEW狀態(tài)的線程此時尚未啟動。只是通過new Thread()創(chuàng)建了線程對象,并未調(diào)用start()方法
2)RUNNABLE:Java線程的 RUNNABLE 狀態(tài)其實(shí)是包括了傳統(tǒng)操作系統(tǒng)線程的 就緒(ready) 和 運(yùn)行(running) 兩個狀態(tài)的。處于 RUNNABLE 狀態(tài)的線程可能在 Java 虛擬機(jī)中運(yùn)行,也有可能在等待 CPU 分配資源
3)BLOCKED:阻塞狀態(tài)。處于 BLOCKED 狀態(tài)的線程正等待鎖的釋放以進(jìn)入同步區(qū),就好比你去食堂打飯,只有一個窗口你就得排隊(duì),等前邊的人結(jié)束之后你完成打飯
4)WAITING :等待狀態(tài)。處于等待狀態(tài)的線程變成 RUNNABLE 狀態(tài)需要其他線程喚醒
可以通過調(diào)用一下三個方法進(jìn)入等待狀態(tài):
- Object.wait():使當(dāng)前線程處于等待狀態(tài)直到另一個線程喚醒它;
- Thread.join():使當(dāng)前線程等待另一個線程執(zhí)行完畢之后再繼續(xù)執(zhí)行,底層調(diào)用的是 Object 實(shí)例的 wait() 方法;
- LockSupport.park():除非獲得調(diào)用許可,否則禁用當(dāng)前線程進(jìn)行線程調(diào)度
5)TIMED_WAITING:超時等待狀態(tài)。線程等待一個具體的時間,時間到后會被自動喚醒。
調(diào)用如下方法會使線程進(jìn)入超時等待狀態(tài):
- Thread.sleep(long millis):使當(dāng)前線程睡眠指定時間,sleep() 方法不會釋放當(dāng)前鎖,但會讓出 CPU,所以其他不需要爭奪鎖的線程可以獲取 CPU 執(zhí)行;
- Object.wait(long timeout):線程休眠指定時間,等待期間可以通過 notify() / notifyAll() 喚醒;
- Thread.join(long millis):等待當(dāng)前線程最多執(zhí)行 millis 毫秒,如果 millis 為 0,則會一直執(zhí)行;
- LockSupport.parkNanos(long nanos): 除非獲得調(diào)用許可,否則禁用當(dāng)前線程進(jìn)行線程調(diào)度指定時間;
- LockSupport.parkUntil(long deadline):同上,也是禁止線程進(jìn)行調(diào)度指定時間;
6)TERMINATED:終止?fàn)顟B(tài)。此時線程已執(zhí)行完畢。
其實(shí)等待和鎖定狀態(tài)可以被籠統(tǒng)的稱為阻塞狀態(tài),就是停著不動了嘛,在回答面試題時建議回答6種狀態(tài)版本,就是是JDK源碼中定義的,一來有官方支持,二來證明咱看過一點(diǎn)源碼。
狀態(tài)轉(zhuǎn)換
- 新建狀態(tài)的線程調(diào)用start方法進(jìn)入到運(yùn)行狀態(tài)
- 運(yùn)行狀態(tài)線程如果遇到Object.wait()、Thread.join()或者LockSupport.park()方法則會放棄CPU執(zhí)行權(quán)進(jìn)入等待狀態(tài),這個裝需要被喚醒之后才會再次進(jìn)入就緒狀態(tài)獲得到CPU時間片進(jìn)入運(yùn)行狀態(tài)
- 運(yùn)行狀態(tài)的線程遇到Thread.sleep(long)、Object.wait(long)、Thread.join(long)等方法,也就是可以傳入時間的,就會進(jìn)入超時等待狀態(tài),達(dá)到時間之后就會自動進(jìn)入就緒狀態(tài),當(dāng)CPU執(zhí)行就進(jìn)入運(yùn)行狀態(tài)
- 運(yùn)行狀態(tài)的線程如果被同步代碼塊或者同步方法包裹,執(zhí)行時如果釋放鎖資源,就會進(jìn)入阻塞狀態(tài)或者叫鎖定狀態(tài),只有再次獲取到鎖資源時才會進(jìn)入就緒狀態(tài),等到CPU時間片后進(jìn)入運(yùn)行狀態(tài)
- 執(zhí)行完的線程就會進(jìn)入終止?fàn)顟B(tài),線程結(jié)束
線程之間的狀態(tài)轉(zhuǎn)換可以參考下圖

Thread類詳解
成員變量
|
變量名 |
類型 |
作用 |
|
name |
volatile String |
線程名稱 |
|
priority |
int |
線程的優(yōu)先級,默認(rèn)為5,范圍1-10 |
|
threadQ |
Thread |
|
|
eetop |
long |
|
|
single_step |
boolean |
是否單步執(zhí)行 |
|
daemon |
boolean |
守護(hù)線程狀態(tài),默認(rèn)為false |
|
stillborn |
boolean |
JVM狀態(tài),默認(rèn)為false |
|
target |
target |
將被執(zhí)行的Runnable實(shí)現(xiàn)類 |
|
group |
ThreadGroup |
當(dāng)前線程的線程組 |
|
contextClassLoader |
ClassLoader |
這個線程上下文的類加載器 |
|
inheritedAccessControlContext |
AccessControlContext |
該線程繼承的AccessControlContext |
|
threadInitNumber |
static int |
用于匿名線程的自動編號 |
|
threadLocals |
ThreadLocal.ThreadLocalMap |
屬于此線程的ThreadLocal,這個映射關(guān)系通過ThreadLocal維持 |
|
inheritableThreadLocals |
ThreadLocal.ThreadLocalMap |
這個線程的InheritableThreadLocal,其映射關(guān)系通過InheritableThreadLocal維持 |
|
stackSize |
long |
此線程的請求的堆棧的大小,如果創(chuàng)建者的請求堆棧大小為0,則不指定堆棧大小,由jvm來自行決定。一些jvm會忽略這個參數(shù)。 |
|
nativeParkEventPointer |
long |
在本機(jī)線程終止后持續(xù)存在的jvm私有狀態(tài)。 |
|
tid |
long |
線程的ID |
|
threadSeqNumber |
static long |
用于生成線程的ID |
|
threadStatus |
volatile int |
java線程狀態(tài),0表示未啟動 |
|
parkBlocker |
volatile Object |
提供給LockSupport調(diào)用的參數(shù) |
|
blocker |
volatile Interruptible |
此線程在可中斷的IO操作中被阻塞的對象,阻塞程序的中斷方法應(yīng)該在設(shè)置了這個線程中斷狀態(tài)之后被調(diào)用 |
常量
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
|
常量名 |
數(shù)據(jù)類型 |
作用 |
|
MIN_PRIORITY |
int |
線程最低優(yōu)先級 |
|
NORM_PRIORITY |
int |
分配給線程的默認(rèn)優(yōu)先級 |
|
MAX_PRIORITY |
int |
線程最大優(yōu)先級 |
Thread構(gòu)造方法
從源碼看出Thread類一共有9個構(gòu)造方法,除第三個為default修飾【同包可用】,其他都是public

|
構(gòu)造方法 |
作用 |
|
Thread() |
分配新的Thread對象 |
|
Thread(Runnable target) |
傳入Runnable接口實(shí)現(xiàn)類,之后由JVM啟動線程 |
|
Thread(Runnable target, AccessControlContext acc) |
在傳入Runnable的時候還可以指定AccessControlContext |
|
Thread(ThreadGroup group, Runnable target) |
指定線程組和Runnable接口 |
|
Thread(String name) |
指定線程名字,默認(rèn)是【Thread-下一個線程編號,從0開始】 |
|
Thread(ThreadGroup group, String name) |
指定線程組和線程名字 |
|
Thread(Runnable target, String name) |
指定Runnable接口和線程名 |
|
Thread(ThreadGroup group, Runnable target, String name) |
指定線程組,Runnable接口和線程名 |
|
Thread(ThreadGroup group, Runnable target, String name,long stackSize) |
指定線程組,Runnable接口,線程名和此線程請求的堆棧大小,默認(rèn)為0 |
Thread常用方法
|
方法 |
返回值類型 |
作用 |
|
start() |
void |
啟動線程 |
|
run() |
void |
重寫的Runnable接口方法,封裝線程的功能體 |
|
currentThread() |
Thread |
靜態(tài)方法,獲取當(dāng)前線程 |
|
getName() |
String |
獲取線程名 |
|
setName(String name) |
void |
設(shè)置線程名 |
|
yield() |
void |
主動釋放當(dāng)前線程的執(zhí)行權(quán) |
|
join() |
void |
在線程中插入執(zhí)行另一個線程,該線程被阻塞,直到插入執(zhí)行的線程完全執(zhí)行完畢以后,該線程才繼續(xù)執(zhí)行下去 |
|
sleep(long millis) |
void |
線程休眠一段時間 |
|
isAlive() |
boolean |
判斷線程是否還存活 |
|
isDaemon() |
boolean |
判斷是否為守護(hù)線程 |
|
stop() |
void |
過時方法。當(dāng)執(zhí)行此方法時,強(qiáng)制結(jié)束當(dāng)前線程,因過于粗暴,會引發(fā)很多問題所以棄用 |
|
setDaemon(boolean on) |
void |
設(shè)置為守護(hù)線程 |
|
getPriority() |
int |
獲取線程優(yōu)先級 |
|
setPriority(int newPriority) |
void |
設(shè)置線程優(yōu)先級 |
設(shè)置線程名
實(shí)現(xiàn)類:
public class RunnableImpl implements Runnable{
@Override
public void run() {
// 輸出10以內(nèi)偶數(shù)
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
// 獲取當(dāng)前線程
Thread thread = Thread.currentThread();
// 獲取線程名
String threadName = thread.getName();
System.out.println(threadName + "===>" + i);
}
}
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創(chuàng)建實(shí)現(xiàn)類對象
RunnableImpl runnable = new RunnableImpl();
// 2、 創(chuàng)建線程對象,并指定線程名
Thread t1 = new Thread(runnable, "線程1");
// 3、啟動線程
t1.start();
System.out.println(Thread.currentThread().getName() + "主線程");
}
}
運(yùn)行結(jié)果:

或者通過setName()方法設(shè)置線程名
public class RunnableMain {
public static void main(String[] args) {
// 1、創(chuàng)建實(shí)現(xiàn)類對象
RunnableImpl runnable = new RunnableImpl();
// 2、 創(chuàng)建線程對象,不指定名字
Thread t1 = new Thread(runnable);
// 設(shè)置線程名
t1.setName("線程1");
// 3、啟動線程
t1.start();
System.out.println(Thread.currentThread().getName() + "主線程");
}
}
如果不設(shè)置線程名,默認(rèn)為【"Thread-" + nextThreadNum()】,nextThreadNum方法使用 threadInitNumber靜態(tài)變量,默認(rèn)從0開始,每次+1

不設(shè)置線程名運(yùn)行效果如下

sleep方法
sleep方法可以讓線程阻塞指定的毫秒數(shù)。時間到了后,線程進(jìn)入就緒狀態(tài)。sleep可用來研模擬網(wǎng)絡(luò)延時,倒計(jì)時等。每一個對象都有一個鎖,sleep不會釋放鎖,鎖的概念后邊會詳細(xì)講解
實(shí)現(xiàn)類:
public class RunnableImpl implements Runnable{
@Override
public void run() {
// 輸出10以內(nèi)偶數(shù)
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
try {
// 休眠1秒
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "===>" + i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創(chuàng)建實(shí)現(xiàn)類對象
RunnableImpl runnable = new RunnableImpl();
// 2、 創(chuàng)建線程對象,不指定名字
Thread t1 = new Thread(runnable,"線程1");
// 3、啟動線程
t1.start();
}
}
運(yùn)行結(jié)果:

"善用"sleep年入百萬不是夢:

yield方法
提出申請釋放CPU資源,至于能否成功釋放取決于JVM決定,調(diào)用yield()方法后,線程仍然處于RUNNABLE狀態(tài),線程不會進(jìn)入阻塞狀態(tài),保留了隨時被調(diào)用的權(quán)利
實(shí)現(xiàn)類:
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開始執(zhí)行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "執(zhí)行結(jié)束");
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創(chuàng)建實(shí)現(xiàn)類對象
RunnableImpl runnable = new RunnableImpl();
// 2、 創(chuàng)建線程對象,不指定名字
Thread t1 = new Thread(runnable,"線程1");
Thread t2 = new Thread(runnable,"線程2");
// 3、啟動線程
t1.start();
t2.start();
}
}
運(yùn)行結(jié)果:
第五次執(zhí)行是線程2執(zhí)行開始結(jié)束后輸出的線程1開始結(jié)束,這就說明CPU并沒有切換到別的線程,說明并沒有釋放CPU資源

join方法
將當(dāng)前的線程掛起,當(dāng)前線程阻塞,待其他的線程執(zhí)行完畢,當(dāng)前線程才能執(zhí)行,可以把join()方法理解為插隊(duì),誰插到前面,誰先執(zhí)行
在很多情況下,主線程創(chuàng)建并啟動子線程,如果子線程中要進(jìn)行大量的耗時運(yùn)算,主線程將可能早于子線程結(jié)束。如果主線程需要知道子線程的執(zhí)行結(jié)果時,就需要等待子線程執(zhí)行結(jié)束了。主線程可以sleep(xx),但這樣的xx時間不好確定,因?yàn)樽泳€程的執(zhí)行時間不確定,join()方法比較合適這個場景
public class RunnableMain {
public static void main(String[] args) {
// 1、lambda創(chuàng)建線程
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
// 模擬耗時操作
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "join方法===>" + i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
// 2、 啟動線程
t1.start();
try {
// t1調(diào)用join 方法
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("main線程");
}
}
運(yùn)行結(jié)果:

設(shè)置優(yōu)先級
改變、獲取線程的優(yōu)先級。Java提供一個線程調(diào)度器來監(jiān)控程序中啟動后進(jìn)入就緒狀態(tài)的所有線程,線程調(diào)度器按照優(yōu)先級決定應(yīng)該調(diào)度哪個線程來執(zhí)行。線程的優(yōu)先級用數(shù)據(jù)表示,范圍1~10。線程的優(yōu)先級高只是表示他的權(quán)重大,獲取CPU執(zhí)行權(quán)的幾率大。先設(shè)置線程的優(yōu)先級,在執(zhí)行start()方法
public class RunnableMain {
public static void main(String[] args) {
// 1、lambda創(chuàng)建線程
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "線程優(yōu)先級" + Thread.currentThread().getPriority());
},"線程1");
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "線程優(yōu)先級" + Thread.currentThread().getPriority());
},"線程2");
// 2、設(shè)置線程優(yōu)先級
t1.setPriority(1);
t2.setPriority(10);
// 3、 啟動線程
t1.start();
t2.start();
System.out.println("main線程");
}
}
結(jié)束線程
JDK提供的【stop()、destroy()】兩種方法已廢棄,不推薦再使用。推薦線程自動停止下來,就比如上邊的所有案例,都是執(zhí)行完了run方法中的所有代碼之后線程就自然結(jié)束了。如果線程需要循環(huán)執(zhí)行,建議使用一個標(biāo)識位變量進(jìn)行終止,當(dāng)flag=false時,則終止線程運(yùn)行
比如:定義一個名為【線程1】的子線程,當(dāng)主線程執(zhí)行3次循環(huán)之后,線程1停止運(yùn)行
實(shí)現(xiàn)類:
public class RunnableImpl implements Runnable{
// boolean變量標(biāo)記是否需要繼續(xù)執(zhí)行
private boolean flag = true;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
// 循環(huán)執(zhí)行,flag為false時停止
while (flag) {
System.out.println(Thread.currentThread().getName() + "正在運(yùn)行");
}
}
}
測試類:
public class RunnableMain {
public static void main(String[] args) {
RunnableImpl runnable = new RunnableImpl();
Thread t1 = new Thread(runnable, "線程1");
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println("主線程====》" + i);
// 當(dāng)循環(huán)三次時
if(i == 3) {
// 設(shè)置flag值為false
runnable.setFlag(false);
}
}
}
}
總結(jié)
- 掌握多線程的使用場景和術(shù)語
- 熟練創(chuàng)建和啟動線程
- 掌握線程狀態(tài)和狀態(tài)之間轉(zhuǎn)換
- 掌握Thread類中的常用方法如:join、sleep、yield等






