新聞中心
前言
前段時(shí)間推出的Java8新特性文章收到大家廣泛關(guān)注和好評(píng),非常感謝各位支持,這段時(shí)間苦思冥想,決定輸出一波Java多線程技能點(diǎn),希望可以在大家的工作和面試中有所幫助!本篇文章為多線程系列第一章,主要講解一下幾點(diǎn):

多線程好處和應(yīng)用場(chǎng)景
多線程的相關(guān)概念和術(shù)語(yǔ)
Java線程創(chuàng)建方式
Thread類詳解,線程的常用方法
線程5種狀態(tài)和6種狀態(tài),兩種版本解釋
線程狀態(tài)之間轉(zhuǎn)換
Java設(shè)計(jì)者寫過一個(gè)很有影響力的白皮書,用來解釋設(shè)計(jì)的初衷,并發(fā)布了一個(gè)簡(jiǎn)短的摘要,分為11個(gè)術(shù)語(yǔ):
- 簡(jiǎn)單性
- 面向?qū)ο?/li>
- 分布式
- 健壯性
- 安全性
- 體系結(jié)構(gòu)中立
- 可移植性
- 解釋型
- 高性能
- 多線程
- 動(dòng)態(tài)性
其中多線程就是本次要接觸的,白皮書中對(duì)多線程的解釋:
多線程可以帶來更好的交互響應(yīng)和實(shí)時(shí)行為。
如今,我們非常關(guān)注并發(fā)性,因?yàn)槟柖尚袑⑼杲Y(jié)。我們不再追求更快的處理器,而是著眼于獲得更多的處理器,而且要讓它們一直保持工作。不過,可以看到,大多數(shù)編程語(yǔ)言對(duì)于這個(gè)問題并沒有顯示出足夠的重視。Java在當(dāng)時(shí)很超前。它是第一個(gè)支持并發(fā)程序設(shè)計(jì)的主流語(yǔ)言。從白皮書中可以看到,它的出發(fā)點(diǎn)稍有些不同。當(dāng)時(shí),多核處理器還很神秘,而Web編程才剛剛起步,處理器要花很長(zhǎng)時(shí)間等待服務(wù)器響應(yīng),需要并發(fā)程序設(shè)計(jì)來確保用戶界面不會(huì)"凍住"。并發(fā)程序設(shè)計(jì)絕非易事,不過Java在這方面表現(xiàn)很出色,可以很好地管理這個(gè)工作。
在操作系統(tǒng)中有多任務(wù)【multitasking】,在同一刻運(yùn)行多個(gè)程序【應(yīng)用】的能力。例如,在聽音樂的同時(shí)可以邊打游戲,邊寫代碼。如今我們的電腦大多都是多核CPU,但是,并發(fā)執(zhí)行的進(jìn)程【正在執(zhí)行的應(yīng)用】數(shù)目并不是由CPU數(shù)目制約的。操作系統(tǒng)將CPU的時(shí)間片分配給每一個(gè)進(jìn)程,給人并行處理的感覺。
相關(guān)概念
程序【program】:為了完成特定任務(wù),用某種語(yǔ)言編寫的一組指令的集合。程序就是一堆代碼,一組數(shù)據(jù)和指令集,是一個(gè)靜態(tài)的概念。就說我們程序員寫的那玩意。比如:安裝在電腦或者手機(jī)上的各種軟件,今日頭條、抖音、懂車帝等,如果一個(gè)程序支持多線程,這個(gè)程序就是一個(gè)多線程程序
進(jìn)程【Process】:是程序的一次執(zhí)行過程或者說是正在運(yùn)行的程序,是一個(gè)動(dòng)態(tài)概念,進(jìn)程存在生命周期,也就是說程序隨著程序的終止而銷毀
線程【Thread】:線程是進(jìn)程中的實(shí)際運(yùn)作的單位,是進(jìn)程的一條流水線,是程序的實(shí)際執(zhí)行者,是最小的執(zhí)行單位。通常在一個(gè)進(jìn)程中可以包含若干個(gè)線程,當(dāng)然一個(gè)進(jìn)程中至少有一個(gè)線程。線程是CPU調(diào)度和執(zhí)行的最小單位
CPU時(shí)間片:時(shí)間片即CPU分配給各個(gè)程序的時(shí)間,每個(gè)進(jìn)程被分配一個(gè)時(shí)間段,稱作它的時(shí)間片,即該進(jìn)程允許運(yùn)行的時(shí)間,使各個(gè)程序從表面上看是同時(shí)進(jìn)行的,如果在時(shí)間片結(jié)束時(shí)進(jìn)程還在運(yùn)行,則CPU將被剝奪并分配給另一個(gè)進(jìn)程。如果進(jìn)程在時(shí)間片結(jié)束前阻塞或結(jié)束,則CPU當(dāng)即進(jìn)行切換。而不會(huì)造成CPU資源浪費(fèi)
并行【parallel】:多個(gè)任務(wù)同時(shí)進(jìn)行,并行必須有多核才能實(shí)現(xiàn),否則只能是并發(fā),比如:多名學(xué)生有問題,同時(shí)有多名老師可以輔導(dǎo)解決
串行【serial】:一個(gè)程序處理完當(dāng)前進(jìn)程,按照順序接著處理下一個(gè)進(jìn)程,一個(gè)接著一個(gè)進(jìn)行,比如:多名學(xué)生有問題,只有一名老師,需要挨個(gè)解決
并發(fā)【concurrency】:同一個(gè)對(duì)象被多個(gè)線程同時(shí)操作。(這是一種假并行。即一個(gè)CPU的情況下,在同一個(gè)時(shí)間點(diǎn),CPU只能執(zhí)行一個(gè)代碼,因?yàn)榍袚Q的很快,所以就有同時(shí)執(zhí)行的錯(cuò)覺),比如:多名學(xué)生有問題,只有一個(gè)老師,他一會(huì)處理A同學(xué),一會(huì)處理B同學(xué),一會(huì)處理C同學(xué),頻繁切換,看起來好似在同時(shí)處理學(xué)生問題
多線程意義
實(shí)際應(yīng)用中,多線程非常有用,例如,QQ音樂就是一個(gè)多線程程序,我們可以一邊聽音樂,一般下載音樂,還可以同時(shí)播放MV等非常方便。一個(gè)Web服務(wù)器通過多線程同時(shí)處理多個(gè)請(qǐng)求,比如Tomcat就是多線程的。
注意:程序會(huì)因?yàn)橐攵嗑€程而變的復(fù)雜,多線程同時(shí)會(huì)帶來一些問題,需要我們解決
多線程應(yīng)用場(chǎng)景
- 程序需要同時(shí)執(zhí)行兩個(gè)或多個(gè)任務(wù)。
- 程序需要實(shí)現(xiàn)一些需要等待的任務(wù)時(shí),如用戶輸入、文件讀寫操作、網(wǎng)絡(luò)操作、搜索等。
- 需要一些后臺(tái)運(yùn)行的程序時(shí)。
多線程多數(shù)在瀏覽器、Web服務(wù)器、數(shù)據(jù)庫(kù)、各種專用服務(wù)器【如游戲服務(wù)器】、分布式計(jì)算等場(chǎng)景出現(xiàn)。
在使用Java編寫后臺(tái)服務(wù)時(shí),如果遇到并發(fā)較高、需要后臺(tái)任務(wù)、需要長(zhǎng)時(shí)間處理大數(shù)據(jù)等情況都可以創(chuàng)建線程單獨(dú)的線程處理這些事項(xiàng),多線程的目的就在于提高處理速度,減少用戶等待時(shí)間
- 后臺(tái)記錄日志,創(chuàng)建額外線程來記錄日志
- 大量用戶請(qǐng)求,創(chuàng)建多個(gè)線程共同處理請(qǐ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)建線程對(duì)象,調(diào)用start方法啟動(dòng)線程
- 啟動(dòng)線程之后,線程不一定會(huì)立即執(zhí)行,需要得到CPU分配的時(shí)間片,也就是拿到CPU執(zhí)行權(quán)限才會(huì)執(zhí)行
JDK源碼中,Thread類定義實(shí)現(xiàn)了Runnable接口
所以知道重寫的run方法從哪來的了吧!就是從Runnable接口中來的
需求:創(chuàng)建線程計(jì)算10以內(nèi)的偶數(shù)
線程類:
public class ThreadTest extends Thread{
// run方法是 線程體,啟動(dòng)線程時(shí)會(huì)運(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);
}
}
}
}測(cè)試類:
測(cè)試類中輸出了一句話:主線程
public class ThreadMain {
public static void main(String[] args) {
// 1、創(chuàng)建線程對(duì)象
ThreadTest t1 = new ThreadTest();
// 2、調(diào)用start方法啟動(dòng)線程
t1.start();
System.out.println("主線程");
}
}打印結(jié)果:
實(shí)現(xiàn)Runnable接口
步驟:
- 自定義類實(shí)現(xiàn)Runnable接口
- 實(shí)現(xiàn)run方法
- 創(chuàng)建實(shí)現(xiàn)類對(duì)象
- 創(chuàng)建Thread對(duì)象,在構(gòu)造方法中傳入實(shí)現(xiàn)類對(duì)象作為參數(shù)
- 調(diào)用Thread對(duì)象的start方法啟動(dòng)線程
同樣的需求打印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);
}
}
}
}測(cè)試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創(chuàng)建實(shí)現(xiàn)類對(duì)象
RunnableImpl runnable = new RunnableImpl();
// 2、創(chuàng)建線程對(duì)象,接收實(shí)現(xiàn)類,因?yàn)閷?shí)現(xiàn)類中的run方法承載了線程的功能
Thread t1 = new Thread(runnable);
// 3、啟動(dòng)線程
t1.start();
// 主線程
System.out.println("主線程");
}
}Callable接口
FutureTask類:
RunnableFuture接口:
步驟:
- 新建類實(shí)現(xiàn)Callable接口,并指定泛型類型,類型就是線程計(jì)算之后的結(jié)果的類型
- 實(shí)現(xiàn)call方法,call方法跟run方法類似,不過該方法有返回值和異常拋出,都是來封裝線程功能體的
- 在測(cè)試類中創(chuàng)建實(shí)現(xiàn)類對(duì)象,并且創(chuàng)建 FutureTask 對(duì)象將實(shí)現(xiàn)類對(duì)象當(dāng)做構(gòu)造方法參數(shù)傳入
- 創(chuàng)建Thread線程對(duì)象,將 FutureTask 對(duì)象當(dāng)做構(gòu)造方法參數(shù)傳入,并調(diào)用start方法啟動(dòng)線程
- 可以通過 FutureTask 對(duì)象的get方法獲取線程的運(yùn)算結(jié)果
案例:還是計(jì)算10以內(nèi)的偶數(shù),這一次將計(jì)算結(jié)果返回,因?yàn)橛卸鄠€(gè)數(shù)據(jù)所以返回?cái)?shù)據(jù)用集合存儲(chǔ),則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> {
// 2、線程返回Integer類型數(shù)據(jù),拋出異常
@Override
public Listcall() throws Exception {
Listlist = new ArrayList<>();
for (int i = 0; i < 10; i++) {
if (i % 2 == 0){
// 3、偶數(shù)存儲(chǔ)到集合中
list.add(i);
}
}
// 4、返回集合
return list;
}
}
測(cè)試類:
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)類對(duì)象
CallableImpl callable = new CallableImpl();
// 2、創(chuàng)建 FutureTask對(duì)象傳入 callable
// FutureTask 實(shí)現(xiàn) 了 RunnableFuture,RunnableFuture實(shí)現(xiàn)了Runnable接口和Future接口
FutureTask> task = new FutureTask<>(callable);
// 3、將 task 傳入線程對(duì)象
Thread t1 = new Thread(task);
// 4、啟動(dòng)線程
t1.start();
// 5、獲取線程返回?cái)?shù)據(jù)
try {
Listlist = 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)的方式更適合用來處理多個(gè)線程有共享數(shù)據(jù)的情況】,很多小伙伴也拿去背,這句話怎么看都不對(duì)吧,多線程共享數(shù)據(jù)不加鎖,不同步怎么著也不能避免線程安全問題!
線程開辟
- 開辟線程需要通過Thread類創(chuàng)建對(duì)象
- 啟動(dòng)線程需要Thread對(duì)象調(diào)用start方法
- 線程的功能可以裝在Thread類的run方法或者Runnable接口實(shí)現(xiàn)類的run方法類中
- 線程開辟需要在另一個(gè)線程中開啟,新開辟的線程稱為子線程
小貼士:對(duì)于Java應(yīng)用程序java.exe來講,至少會(huì)存在三個(gè)線程:
main主線程
gc垃圾回收線程
異常處理線程,如果發(fā)生異常會(huì)影響主線程。
線程狀態(tài)
線程的狀態(tài)網(wǎng)上有 5種狀態(tài) 和 6種狀態(tài) 兩個(gè)版本
五種狀態(tài)版本:是基于現(xiàn)代操作系統(tǒng)線程狀態(tài)角度解釋的
新建:當(dāng)一個(gè)Thread類或其子類的對(duì)象被聲明并創(chuàng)建時(shí),新生的線程對(duì)象處于新建狀態(tài)
就緒:處于新建狀態(tài)的線程被start后,將進(jìn)入線程隊(duì)列等待CPU時(shí)間片,此時(shí)它已具備了運(yùn)行的條件,只是沒分配到CPU資源
運(yùn)行:當(dāng)就緒的線程被調(diào)度并獲得CPU資源時(shí),便進(jìn)入運(yùn)行狀態(tài),run方法定義了線程的操作和功能
阻塞:在某種特殊情況下,被人為掛起或執(zhí)行輸入輸出操作時(shí),讓出CPU并臨時(shí)終止自己的執(zhí)行,進(jìn)入阻塞狀態(tài)
死亡:線程完成了它的全部工作或線程被提前強(qiáng)制性地中止或出現(xiàn)異常導(dǎo)致結(jié)束
在JDK5的時(shí)候Thread類中定義了一個(gè)State枚舉類,其中定義了6種線程狀態(tài),這是Java官方定義的Java線程的6種狀態(tài)
1)NEW:處于NEW狀態(tài)的線程此時(shí)尚未啟動(dòng)。只是通過new Thread()創(chuàng)建了線程對(duì)象,并未調(diào)用start()方法
2)RUNNABLE:Java線程的 RUNNABLE 狀態(tài)其實(shí)是包括了傳統(tǒng)操作系統(tǒng)線程的 就緒(ready) 和 運(yùn)行(running) 兩個(gè)狀態(tài)的。處于 RUNNABLE 狀態(tài)的線程可能在 Java 虛擬機(jī)中運(yùn)行,也有可能在等待 CPU 分配資源
3)BLOCKED:阻塞狀態(tài)。處于 BLOCKED 狀態(tài)的線程正等待鎖的釋放以進(jìn)入同步區(qū),就好比你去食堂打飯,只有一個(gè)窗口你就得排隊(duì),等前邊的人結(jié)束之后你完成打飯
4)WAITING :等待狀態(tài)。處于等待狀態(tài)的線程變成 RUNNABLE 狀態(tài)需要其他線程喚醒
可以通過調(diào)用一下三個(gè)方法進(jìn)入等待狀態(tài):
- Object.wait():使當(dāng)前線程處于等待狀態(tài)直到另一個(gè)線程喚醒它;
- Thread.join():使當(dāng)前線程等待另一個(gè)線程執(zhí)行完畢之后再繼續(xù)執(zhí)行,底層調(diào)用的是 Object 實(shí)例的 wait() 方法;
- LockSupport.park():除非獲得調(diào)用許可,否則禁用當(dāng)前線程進(jìn)行線程調(diào)度
5)TIMED_WAITING:超時(shí)等待狀態(tài)。線程等待一個(gè)具體的時(shí)間,時(shí)間到后會(huì)被自動(dòng)喚醒。
調(diào)用如下方法會(huì)使線程進(jìn)入超時(shí)等待狀態(tài):
- Thread.sleep(long millis):使當(dāng)前線程睡眠指定時(shí)間,sleep() 方法不會(huì)釋放當(dāng)前鎖,但會(huì)讓出 CPU,所以其他不需要爭(zhēng)奪鎖的線程可以獲取 CPU 執(zhí)行;
- Object.wait(long timeout):線程休眠指定時(shí)間,等待期間可以通過 notify() / notifyAll() 喚醒;
- Thread.join(long millis):等待當(dāng)前線程最多執(zhí)行 millis 毫秒,如果 millis 為 0,則會(huì)一直執(zhí)行;
- LockSupport.parkNanos(long nanos): 除非獲得調(diào)用許可,否則禁用當(dāng)前線程進(jìn)行線程調(diào)度指定時(shí)間;
- LockSupport.parkUntil(long deadline):同上,也是禁止線程進(jìn)行調(diào)度指定時(shí)間;
6)TERMINATED:終止?fàn)顟B(tài)。此時(shí)線程已執(zhí)行完畢。
其實(shí)等待和鎖定狀態(tài)可以被籠統(tǒng)的稱為阻塞狀態(tài),就是停著不動(dòng)了嘛,在回答面試題時(shí)建議回答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()方法則會(huì)放棄CPU執(zhí)行權(quán)進(jìn)入等待狀態(tài),這個(gè)裝需要被喚醒之后才會(huì)再次進(jìn)入就緒狀態(tài)獲得到CPU時(shí)間片進(jìn)入運(yùn)行狀態(tài)
- 運(yùn)行狀態(tài)的線程遇到Thread.sleep(long)、Object.wait(long)、Thread.join(long)等方法,也就是可以傳入時(shí)間的,就會(huì)進(jìn)入超時(shí)等待狀態(tài),達(dá)到時(shí)間之后就會(huì)自動(dòng)進(jìn)入就緒狀態(tài),當(dāng)CPU執(zhí)行就進(jìn)入運(yùn)行狀態(tài)
- 運(yùn)行狀態(tài)的線程如果被同步代碼塊或者同步方法包裹,執(zhí)行時(shí)如果釋放鎖資源,就會(huì)進(jìn)入阻塞狀態(tài)或者叫鎖定狀態(tài),只有再次獲取到鎖資源時(shí)才會(huì)進(jìn)入就緒狀態(tài),等到CPU時(shí)間片后進(jìn)入運(yùn)行狀態(tài)
- 執(zhí)行完的線程就會(huì)進(jìn)入終止?fàn)顟B(tài),線程結(jié)束
線程之間的狀態(tài)轉(zhuǎn)換可以參考下圖
Thread類詳解
成員變量
|
變量名 |
類型 |
作用 |
|
name |
volatile String |
線程名稱 |
|
priority |
int |
線程的優(yōu)先級(jí),默認(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 |
這個(gè)線程上下文的類加載器 |
|
inheritedAccessControlContext |
AccessControlContext |
該線程繼承的AccessControlContext |
|
threadInitNumber |
static int |
用于匿名線程的自動(dòng)編號(hào) |
|
threadLocals |
ThreadLocal.ThreadLocalMap |
屬于此線程的ThreadLocal,這個(gè)映射關(guān)系通過ThreadLocal維持 |
|
inheritableThreadLocals |
ThreadLocal.ThreadLocalMap |
這個(gè)線程的InheritableThreadLocal,其映射關(guān)系通過InheritableThreadLocal維持 |
|
stackSize |
long |
此線程的請(qǐng)求的堆棧的大小,如果創(chuàng)建者的請(qǐng)求堆棧大小為0,則不指定堆棧大小,由jvm來自行決定。一些jvm會(huì)忽略這個(gè)參數(shù)。 |
|
nativeParkEventPointer |
long |
在本機(jī)線程終止后持續(xù)存在的jvm私有狀態(tài)。 |
|
tid |
long |
線程的ID |
|
threadSeqNumber |
static long |
用于生成線程的ID |
|
threadStatus |
volatile int |
java線程狀態(tài),0表示未啟動(dòng) |
|
parkBlocker |
volatile Object |
提供給LockSupport調(diào)用的參數(shù) |
|
blocker |
volatile Interruptible |
此線程在可中斷的IO操作中被阻塞的對(duì)象,阻塞程序的中斷方法應(yīng)該在設(shè)置了這個(gè)線程中斷狀態(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)先級(jí) |
|
NORM_PRIORITY |
int |
分配給線程的默認(rèn)優(yōu)先級(jí) |
|
MAX_PRIORITY |
int |
線程最大優(yōu)先級(jí) |
Thread構(gòu)造方法
從源碼看出Thread類一共有9個(gè)構(gòu)造方法,除第三個(gè)為default修飾【同包可用】,其他都是public
|
構(gòu)造方法 |
作用 |
|
Thread() |
分配新的Thread對(duì)象 |
|
Thread(Runnable target) |
傳入Runnable接口實(shí)現(xiàn)類,之后由JVM啟動(dòng)線程 |
|
Thread(Runnable target, AccessControlContext acc) |
在傳入Runnable的時(shí)候還可以指定AccessControlContext |
|
Thread(ThreadGroup group, Runnable target) |
指定線程組和Runnable接口 |
|
Thread(String name) |
指定線程名字,默認(rèn)是【Thread-下一個(gè)線程編號(hào),從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接口,線程名和此線程請(qǐng)求的堆棧大小,默認(rèn)為0 |
Thread常用方法
|
方法 |
返回值類型 |
作用 |
|
start() |
void |
啟動(dòng)線程 |
|
run() |
void |
重寫的Runnable接口方法,封裝線程的功能體 |
|
currentThread() |
Thread |
靜態(tài)方法,獲取當(dāng)前線程 |
|
getName() |
String |
獲取線程名 |
|
setName(String name) |
void |
設(shè)置線程名 |
|
yield() |
void |
主動(dòng)釋放當(dāng)前線程的執(zhí)行權(quán) |
|
join() |
void |
在線程中插入執(zhí)行另一個(gè)線程,該線程被阻塞,直到插入執(zhí)行的線程完全執(zhí)行完畢以后,該線程才繼續(xù)執(zhí)行下去 |
|
sleep(long millis) |
void |
線程休眠一段時(shí)間 |
|
isAlive() |
boolean |
判斷線程是否還存活 |
|
isDaemon() |
boolean |
判斷是否為守護(hù)線程 |
|
stop() |
void |
過時(shí)方法。當(dāng)執(zhí)行此方法時(shí),強(qiáng)制結(jié)束當(dāng)前線程,因過于粗暴,會(huì)引發(fā)很多問題所以棄用 |
|
setDaemon(boolean on) |
void |
設(shè)置為守護(hù)線程 |
|
getPriority() |
int |
獲取線程優(yōu)先級(jí) |
|
setPriority(int newPriority) |
void |
設(shè)置線程優(yōu)先級(jí) |
設(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);
}
}
}
}測(cè)試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創(chuàng)建實(shí)現(xiàn)類對(duì)象
RunnableImpl runnable = new RunnableImpl();
// 2、 創(chuàng)建線程對(duì)象,并指定線程名
Thread t1 = new Thread(runnable, "線程1");
// 3、啟動(dòng)線程
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)類對(duì)象
RunnableImpl runnable = new RunnableImpl();
// 2、 創(chuàng)建線程對(duì)象,不指定名字
Thread t1 = new Thread(runnable);
// 設(shè)置線程名
t1.setName("線程1");
// 3、啟動(dòng)線程
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ù)。時(shí)間到了后,線程進(jìn)入就緒狀態(tài)。sleep可用來研模擬網(wǎng)絡(luò)延時(shí),倒計(jì)時(shí)等。每一個(gè)對(duì)象都有一個(gè)鎖,sleep不會(huì)釋放鎖,鎖的概念后邊會(huì)詳細(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);
}
}
}
}
}測(cè)試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創(chuàng)建實(shí)現(xiàn)類對(duì)象
RunnableImpl runnable = new RunnableImpl();
// 2、 創(chuàng)建線程對(duì)象,不指定名字
Thread t1 = new Thread(runnable,"線程1");
// 3、啟動(dòng)線程
t1.start();
}
}運(yùn)行結(jié)果:
"善用"sleep年入百萬(wàn)不是夢(mèng):
yield方法
提出申請(qǐng)釋放CPU資源,至于能否成功釋放取決于JVM決定,調(diào)用yield()方法后,線程仍然處于RUNNABLE狀態(tài),線程不會(huì)進(jìn)入阻塞狀態(tài),保留了隨時(shí)被調(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é)束");
}
}測(cè)試類:
public class RunnableMain {
public static void main(String[] args) {
// 1、創(chuàng)建實(shí)現(xiàn)類對(duì)象
RunnableImpl runnable = new RunnableImpl();
// 2、 創(chuàng)建線程對(duì)象,不指定名字
Thread t1 = new Thread(runnable,"線程1");
Thread t2 = new Thread(runnable,"線程2");
// 3、啟動(dòng)線程
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ì),誰(shuí)插到前面,誰(shuí)先執(zhí)行
在很多情況下,主線程創(chuàng)建并啟動(dòng)子線程,如果子線程中要進(jìn)行大量的耗時(shí)運(yùn)算,主線程將可能早于子線程結(jié)束。如果主線程需要知道子線程的執(zhí)行結(jié)果時(shí),就需要等待子線程執(zhí)行結(jié)束了。主線程可以sleep(xx),但這樣的xx時(shí)間不好確定,因?yàn)樽泳€程的執(zhí)行時(shí)間不確定,join()方法比較合適這個(gè)場(chǎng)景
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 {
// 模擬耗時(shí)操作
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "join方法===>" + i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
// 2、 啟動(dòng)線程
t1.start();
try {
網(wǎng)站題目:結(jié)合多本著作和個(gè)人開發(fā)經(jīng)驗(yàn),整理Java多線程入門手冊(cè)
網(wǎng)頁(yè)鏈接:http://fisionsoft.com.cn/article/djidoip.html


咨詢
建站咨詢
