你好,我是田哥。
昨天,一位朋友在面試中被問(wèn)到Thread類(lèi)相關(guān)內容,個(gè)人覺(jué)得,面試官這么問(wèn),目的是考察咱們是否熟悉Thread類(lèi),是否掌握線(xiàn)程的一些常用方法和一些線(xiàn)程的基礎知識。
可能你沒(méi)遇到過(guò),但誰(shuí)也不能保證自己永遠不會(huì )遇到。
本文主要內:Thread類(lèi)的屬性和常見(jiàn)方法。
希望本文能給你帶來(lái)點(diǎn)點(diǎn)幫助。
咱們話(huà)不多說(shuō),直接開(kāi)干。
Thread(java.lang.Thread)線(xiàn)程,我們可以通過(guò)Thread類(lèi)創(chuàng )建線(xiàn)程:
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World!!!");
}
});
thread.start();
下面我們來(lái)看看Thread的核心屬性和常見(jiàn)方法:
1、線(xiàn)程id
private long tid;
此屬性用于保存線(xiàn)程的ID。這是一個(gè)private類(lèi)型的屬性,外部只能使用getId()方法訪(fǎng)問(wèn)線(xiàn)程的ID。方法getId(),獲取線(xiàn)程ID,線(xiàn)程ID由JVM進(jìn)行管理,在進(jìn)程內唯一。比如,1.2節的實(shí)例中,所輸出的main線(xiàn)程的ID為1。
2、線(xiàn)程名稱(chēng)
private String name;
該屬性保存一個(gè)Thread線(xiàn)程實(shí)例的名字。
方法一:public final String getName(),獲取線(xiàn)程名稱(chēng)。
方法二:public final void setName(String name),設置線(xiàn)程名稱(chēng)。
方法三:Thread(String threadName),通過(guò)此構造方法給線(xiàn)程設置一個(gè)定制化的名字。
3、線(xiàn)程優(yōu)先級
private int priority;
保存一個(gè)Thread線(xiàn)程實(shí)例的優(yōu)先級。
方法一:public final int getPriority(),獲取線(xiàn)程優(yōu)先級。
方法二:public final void setPriority(int priority),設置線(xiàn)程優(yōu)先級。
Java線(xiàn)程的最大優(yōu)先級值為10,最小值為1,默認值為5。
這三個(gè)優(yōu)先級值為三個(gè)常量值,在Thread類(lèi)中使用類(lèi)常量定義,三個(gè)類(lèi)常量如下:
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
4、是否為守護線(xiàn)程
private boolean daemon=false;
該屬性保存Thread線(xiàn)程實(shí)例的守護狀態(tài),默認為false,表示是普通的用戶(hù)線(xiàn)程,而不是守護線(xiàn)程。
方法:public final void setDaemon(boolean on),將線(xiàn)程實(shí)例標記為守護線(xiàn)程或用戶(hù)線(xiàn)程,如果參數值為true,那么將線(xiàn)程實(shí)例標記為守護線(xiàn)程。
守護線(xiàn)程是在進(jìn)程運行時(shí)提供某種后臺服務(wù)的線(xiàn)程,比如垃圾回收(GC)線(xiàn)程。
5、線(xiàn)程的狀態(tài)
private int threadStatus;
該屬性以整數的形式保存線(xiàn)程的狀態(tài)。
方法:public Thread.State getState(),返回表示當前線(xiàn)程的執行狀態(tài),為新建、就緒、運行、阻塞、結束等狀態(tài)中的一種。
Thread的內部靜態(tài)枚舉類(lèi)State用于定義Java線(xiàn)程的所有狀態(tài),具體如下:
public enum State {
//新建
NEW,
//就緒、運行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超時(shí)等待
TIMED_WAITING,
//運行結束
TERMINATED;
}
public State getState() {
// 獲取當前線(xiàn)程狀態(tài)
return sun.misc.VM.toThreadState(threadStatus);
}
}
在Java線(xiàn)程的狀態(tài)中,就緒狀態(tài)和運行狀態(tài)在內部用同一種狀態(tài)RUNNABLE表示。就緒狀態(tài)表示線(xiàn)程具備運行條件,正在等待獲取CPU時(shí)間片;運行狀態(tài)表示線(xiàn)程已經(jīng)獲取了CPU時(shí)間片,CPU正在執行線(xiàn)程代碼邏輯。
如何獲取當前線(xiàn)程名稱(chēng):

public class ThreadMethodsDemo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("當前線(xiàn)程名稱(chēng):"+Thread.currentThread().getName());
}
},"Java后端技術(shù)全棧").start();
}
}
輸出:
當前線(xiàn)程名稱(chēng):Java后端技術(shù)全棧
上面只是舉例一種方式,還有其他方式也可以獲取。我們還是要搞清楚什么地方能設置線(xiàn)程的名稱(chēng)。
注意Thread中有個(gè)屬性name,這就是線(xiàn)程名稱(chēng)。
其實(shí)能設置線(xiàn)程名稱(chēng)的兩個(gè)地方:
第一個(gè),Thread中構造方法中能設置name都,最后都是調用這個(gè)init方法進(jìn)行name設置的。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
//設置name的值,線(xiàn)程的名稱(chēng)
this.name = name;
//....省略
}
第二個(gè),也就是setName(String name);方法
public final synchronized void setName(String name) {
checkAccess();
if (name == null) {
throw new NullPointerException("name cannot be null");
}
//設置線(xiàn)程name
this.name = name;
if (threadStatus != 0) {
setNativeName(name);
}
}
使用:
public class ThreadMethodsDemo {
public static void main(String[] args) {
test1();
test2();
}
private static void test1(){
new Thread(new Runnable() {
@Override
public void run() {
//輸出:當前線(xiàn)程名稱(chēng):Java后端技術(shù)全棧
System.out.println("當前線(xiàn)程名稱(chēng):"+Thread.currentThread().getName());
}
},"Java后端技術(shù)全棧").start();
}
private static void test2(){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//輸出:當前線(xiàn)程名稱(chēng):老田
System.out.println("當前線(xiàn)程名稱(chēng):"+Thread.currentThread().getName());
}
});
thread.setName("老田");
thread.start();
}
}
建議:我們在使用的時(shí)候建議設置線(xiàn)程名稱(chēng),當出問(wèn)題的時(shí)候可以更好地辨別屬于哪個(gè)線(xiàn)程,加速問(wèn)題排除效率。
Thread.sleep(times)使當前線(xiàn)程從Running狀態(tài)放棄處理器進(jìn)入Block狀態(tài),休眠times毫秒,再返回Runnable狀態(tài)。
private static void test3(){
new Thread(new Runnable() {
@Override
public void run() {
SimpleDateFormat format=new SimpleDateFormat("hh:mm:ss");
//輸出系統時(shí)間的時(shí)分秒。每2秒顯示一次??赡軙?huì )出現跳秒的情況,因為阻塞1秒過(guò)后進(jìn)入runnable狀態(tài),
//等待分配時(shí)間片進(jìn)入running狀態(tài)后還需要一點(diǎn)時(shí)間
while(true){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(format.format(new Date()));
}
}
}).start();
}
運行:

注意:當一個(gè)線(xiàn)程處于睡眠阻塞時(shí),若被其他線(xiàn)程調用.interrupt方法中斷,則sleep方法會(huì )拋出InterruptedException異常。從上面的try--catch就能看到捕獲的是
InterruptedException中斷異常區。
看下面這段有意思的代碼,啟動(dòng)兩個(gè)子線(xiàn)程,A線(xiàn)程先睡會(huì ),B線(xiàn)程去執行,等B線(xiàn)程執行玩了后中斷A線(xiàn)程。
private static void test4() {
/*
* 表演者:處于睡眠阻塞的線(xiàn)程。
* 當一個(gè)方法中的局部?jì)炔款?lèi)中需要引用該方法的其他局部變量,那么這個(gè)變量必須是final的
*/
final Thread lin = new Thread() {
public void run() {
System.out.println("林:剛美完容,睡覺(jué)吧!");
try {
// 當一個(gè)線(xiàn)程處于睡眠阻塞時(shí),若被其他線(xiàn)程調用interrupt()方法中斷,
// 則sleep()方法會(huì )拋出 InterruptedException異常
Thread.sleep(100000000);
} catch (InterruptedException e) {
System.out.println("林:干嘛呢!都破了相了!");
}
}
};
/* 表演者:中斷睡眠阻塞的線(xiàn)程*/
Thread huang = new Thread() {
public void run() {
System.out.println("黃:開(kāi)始砸墻!");
for (int i = 0; i < 5; i++) {
System.out.println("黃:80!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
System.out.println("咣當!");
System.out.println("黃:搞定!");
// 中斷lin的睡眠阻塞
lin.interrupt();
}
};
lin.start();
huang.start();
}
運行結果:

在Thread類(lèi)中有個(gè)屬性、三個(gè)常量、setPriority()方法:
//優(yōu)先級
private int priority;
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
//newPriority檢查,小于1大于10都不行
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
優(yōu)先級被劃分為1-10,1最低10最高。優(yōu)先級越高的線(xiàn)程被分配時(shí)間片的機會(huì )越多,那么被CPU執行的機會(huì )就越多。
看下面這段代碼:
private static void setPriority() {
// 最高優(yōu)先級的線(xiàn)程
Thread max = new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("max "+Thread.currentThread().getName());
}
}
};
max.setName("max的線(xiàn)程");
// 最低優(yōu)先級的線(xiàn)程
Thread min = new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("min "+Thread.currentThread().getName());
}
}
};
min.setName("min的線(xiàn)程");
// 默認優(yōu)先級的線(xiàn)程
Thread norm = new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("norm "+Thread.currentThread().getName());
}
}
};
norm.setName("norm的線(xiàn)程");
// 設置了優(yōu)先級也不能100%控制線(xiàn)程調度。
// 只是最大程度的告知線(xiàn)程調度以更多的幾率分配時(shí)間片給線(xiàn)程優(yōu)先級高的線(xiàn)程
max.setPriority(Thread.MAX_PRIORITY);
min.setPriority(Thread.MIN_PRIORITY);
// 這項設置可以省略,默認情況下就是該值
norm.setPriority(Thread.NORM_PRIORITY);
min.start();
norm.start();
max.start();
}
運行結果就不貼了,因為每次運行的結果不一樣。因為這家伙不靠譜。
Java線(xiàn)程可以有優(yōu)先級的設定,高優(yōu)先級的線(xiàn)程比低優(yōu)先級的線(xiàn)程有更高的幾率得到執行(不完全正確,請參考下面的“線(xiàn)程優(yōu)先級的問(wèn)題“)。
記住當線(xiàn)程的優(yōu)先級沒(méi)有指定時(shí),所有線(xiàn)程都攜帶普通優(yōu)先級。 優(yōu)先級可以用從1到10的范圍指定。10表示最高優(yōu)先級,1表示最低優(yōu)先級,5是普通優(yōu)先級。 記住優(yōu)先級最高的線(xiàn)程在執行時(shí)被給予優(yōu)先。但是不能保證線(xiàn)程在啟動(dòng)時(shí)就進(jìn)入運行狀態(tài)。 與在線(xiàn)程池中等待運行機會(huì )的線(xiàn)程相比,當前正在運行的線(xiàn)程可能總是擁有更高的優(yōu)先級。 由調度程序決定哪一個(gè)線(xiàn)程被執行。 t.setPriority()用來(lái)設定線(xiàn)程的優(yōu)先級。 記住在線(xiàn)程開(kāi)始方法被調用之前,線(xiàn)程的優(yōu)先級應該被設定。 你可以使用常量,如MIN_PRIORITY,MAX_PRIORITY,NORM_PRIORITY來(lái)設定優(yōu)先級。
對于線(xiàn)程優(yōu)先級,我們需要注意:
join()是Thread類(lèi)的一個(gè)方法,根據jdk文檔的定義,join()方法的作用,是等待這個(gè)線(xiàn)程結束,即當前線(xiàn)程等待另一個(gè)調用join()方法的線(xiàn)程執行結束后再往下執行。通常用于在main主線(xiàn)程內,等待其它調用join()方法的線(xiàn)程執行結束再繼續執行main主線(xiàn)程。
在Thread類(lèi)中有三個(gè)join方法,可能很多人也就只用過(guò)無(wú)參數的那個(gè)方法,其實(shí)Thread里是有三個(gè)方法的:

public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis, int nanos) throws InterruptedException {
//...
join(millis);
}
//最終的join方法實(shí)現
public final synchronized void join(long millis)throws InterruptedException {
//當前時(shí)間戳
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
//等待0, Object中wait方法,會(huì )釋放對象的鎖
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
//等待delay, Object中wait方法,會(huì )釋放對象的鎖
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
/**
* Tests if this thread is alive. A thread is alive if it has
* been started and has not yet died.
* 線(xiàn)程是否活著(zhù)
* @return <code>true</code> if this thread is alive;
* <code>false</code> otherwise.
*/
public final native boolean isAlive();//一看注釋便知道
都知道這個(gè)方法使用了同步鎖修飾,然后用到了wait方法。那就必然會(huì )想到notify和notifyAll方法。那什么時(shí)候執行喚醒這個(gè)操作呢?
下面是JVM層面的代碼:
//一個(gè)c++函數:
void JavaThread::exit(bool destroy_vm, ExitType exit_type) ;
//里面有一個(gè)賊不起眼的一行代碼
ensure_join(this);
static void ensure_join(JavaThread* thread) {
Handle threadObj(thread, thread->threadObj());
ObjectLocker lock(threadObj, thread);
thread->clear_pending_exception();
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
java_lang_Thread::set_thread(threadObj(), NULL);
//同志們看到了沒(méi),別的不用看,就看這一句
//這里就是喚醒操作
lock.notify_all(thread);
thread->clear_pending_exception();
}
先來(lái)個(gè)案例演示一下:
圖片下載和圖片顯示,圖片顯示需要等待圖片下載完成后才可以顯示
public class JoinDemo {
// 判斷照片是否下載完成
private static boolean isFinish = false;
public static void main(String[] args) {
// 下載圖片的線(xiàn)程
final Thread download = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("download:開(kāi)始下載圖片");
for (int i = 0; i <= 100; ) {
i = i + 10;//為了演示
System.out.println("download:已完成" + i + "%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("download:圖片下載完畢");
isFinish = true;
}
});
download.start();
// 用于顯示圖片的線(xiàn)程
Thread showImg = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("show:準備顯示圖片");
// 等待下載線(xiàn)程工作結束后,再執行下面的代碼,
try {
// 此時(shí)顯示圖片的線(xiàn)程就進(jìn)入阻塞狀態(tài),等待download線(xiàn)程運行結束,
// 才能執行下面的代碼。注意千萬(wàn)不要在永遠也死不了的線(xiàn)程上等待
download.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!isFinish) {
throw new RuntimeException("show:圖片還沒(méi)有下載完");
}
System.out.println("show:圖片顯示完成!");
}
});
showImg.start();
}
}
最后輸出:

都知道線(xiàn)程的啟動(dòng)是調用start()方法,繼續說(shuō)說(shuō)這個(gè)方法,在Thread中start方法:
public synchronized void start() {
//這里private volatile int threadStatus = 0;初始化的時(shí)候就是0
//如果這里不為0的話(huà)就拋異常
if (threadStatus != 0)
throw new IllegalThreadStateException();
//把當前線(xiàn)程加入到線(xiàn)程組中
//private ThreadGroup group;就是這么個(gè)東西
group.add(this);
//初始化標記位未啟動(dòng)
boolean started = false;
try {
//下面
start0();
//標識為啟動(dòng)狀態(tài)
started = true;
} finally {
try {
//如果沒(méi)開(kāi)啟,標識為啟動(dòng)失敗
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
//這是虛擬機里的start0方法
private native void start0();
private Runnable target;
//建議重寫(xiě)此方法或者實(shí)現使用Runabble的run方法
//如果重寫(xiě)了Thread的run方法,那么start0方法后面會(huì )調用到子類(lèi)重寫(xiě)的run方法
//如果是實(shí)現Runnable接口中的run方法那么就會(huì )執行下面的target,run()
//否則啥都不干
@Override
public void run() {
if (target != null) {
//調用我們通常實(shí)現的業(yè)務(wù)代碼的run方法
target.run();
}
}
start0方法是虛擬機提供的,最后會(huì )調用run方法,最后執行我們在run方法里的業(yè)務(wù)代碼。
關(guān)于Thread類(lèi),看起來(lái)內容不多,但是涉及到的內容卻不少,很多東西千萬(wàn)別小看,有可能下次面試你就遇到了。
另外,歡迎加入我的知識星球,現在已有200+星友了,星球內容:
簡(jiǎn)歷修改、模擬面試、無(wú)限次數技術(shù)提問(wèn)、簡(jiǎn)歷模板、專(zhuān)屬博客、Java后端學(xué)習手冊、面試小抄、每日一題、公眾號付費文免費讀、后端核心知識總結.....
還有:專(zhuān)門(mén)技術(shù)學(xué)習微信群、資料百度盤(pán)群、一對一聊技術(shù)。
聯(lián)系客服