繼承Thread類創(chuàng)建線程:
/**
* 主類
*/
public class ThreadTest {
public static void main(String[] args) {
//創(chuàng)建線程對象
My_Thread my_thread = new My_Thread();
//啟動線程
my_thread.start();
}
}
/**
* 繼承Thread
*/
class My_Thread extends Thread{
@Override
public void run(){ //線程的任務
System.out.println("My_Thread Running");
}
}
直接使用Thread類創(chuàng)建線程:
class ThreadTest02 {
public static void main(String[] args) {
//直接使用Thread創(chuàng)建線程,"My_Thread"是取得線程名
Thread my_thread = new Thread("My_Thread"){
@Override
public void run() { //線程的任務
System.out.println("My_Thread Running");
}
};
//啟動線程
my_thread.start();
}
}
以上的方式都是直接使用Thread類創(chuàng)建線程,并通過start方法啟動線程,但線程并不會立即執(zhí)行,它還需要等待CPU調(diào)度,只有線程獲得CPU控制權(quán),才算是真正在執(zhí)行。
直接使用Thread類的好處是:
方便傳參,可在子類里添加成員變量,通過set方式設置參數(shù)或通過構(gòu)造函數(shù)傳參
直接使用Thread類的缺點處是:
線程的創(chuàng)建和任務代碼冗余在一起。也可能由于繼承了Thread類,故無法再繼承其他類。任務無返回值。
/**
* 主類
*/
public class ThreadTest03 {
public static void main(String[] args) {
RunnableTask task = new RunnableTask();
//創(chuàng)建線程,參數(shù)1 是任務對象; 參數(shù)2 是線程名字,推薦寫上
Thread my_thread = new Thread(task,"My_Thread");
//啟動線程
my_thread.start();
}
}
/**
* Runable接口實現(xiàn)類
*/
class RunnableTask implements Runnable{
@Override
public void run(){ //線程的任務
System.out.println("Thread Running");
}
}
以上的方式是使用Runnable接口的run方法,該方式將任務代碼與線程的創(chuàng)建分離,這樣在多個線程具有相同任務時,就可以使用同一個Runnable接口實現(xiàn),同時該方式的Runnable的實現(xiàn)類也可以繼承其他的類。該方式更靈活,故推薦使用其來創(chuàng)建線程。
但其缺點也是任務無返回值。
//創(chuàng)建任務類,類似于Runnable
public class CallerTask implements Callable<String> {
@Override
public String call() throws Exception {
return "hello thread";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//創(chuàng)建任務對象
FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
//啟動線程
new Thread(futureTask,"My_Thread").start();
//主線程等待"My_Thread"的任務執(zhí)行完畢,并返回結(jié)果
String res = futureTask.get();
System.out.println(res);
}
}
上述代碼實現(xiàn)了Callable接口的call()方法。在main函數(shù)內(nèi)首先創(chuàng)建FutureTask對象(構(gòu)造函數(shù)為CallerTask的實例)。將創(chuàng)建的FutureTask對象作為任務,并放到新創(chuàng)建的線程中啟動。運行完畢后,則可以使用get方法等待線程里的任務執(zhí)行完畢并返回結(jié)果。
Java線程在其生命周期中可能有六種狀態(tài)。根據(jù)Java.lang.Thread類中的枚舉類型State的定義,其狀態(tài)有以下六種:
①NEW:初始狀態(tài),線程已被創(chuàng)建但還未調(diào)用start()方法來進行啟動。
②RUNNABLE:運行狀態(tài),調(diào)用start方法后,線程處于該狀態(tài)。注意,Java線程的運行狀態(tài),實際上包含了操作系統(tǒng)中的就緒狀態(tài)(已獲得除CPU外的一切運行資源,正在等待CPU調(diào)度,獲得CPU控制權(quán))和運行狀態(tài)(獲得CPU控制權(quán),線程真正在執(zhí)行)。因此,即使Java中的線程處于RUNNABLE狀態(tài),也并不意味著該線程就一定正在執(zhí)行(獲得CPU的控制權(quán)),該線程也有可能在等待CPU調(diào)度。
③BLOCKED:阻塞狀態(tài),線程阻塞于鎖,即線程在鎖的競爭中失敗,則處于阻塞狀態(tài)。
④WAITING:等待狀態(tài),該狀態(tài)的線程需要等待其他線程的中斷或通知。
⑤TIME-WAITING:超時等待狀態(tài),該狀態(tài)下的線程也在等待通知,但若在限定時間內(nèi)沒有,其他線程進行通知,那么超過規(guī)定時間的線程就會自動“醒來”,繼續(xù)執(zhí)行run方法內(nèi)的代碼。
⑥TERMINATED:終止狀態(tài),線程執(zhí)行完畢或者線程在執(zhí)行過程中拋出異常,則線程結(jié)束,線程處于終止狀態(tài)。
阻塞狀態(tài)(BLOCKED),是因為其在鎖競爭中失敗而在等待獲得鎖,而等待狀態(tài)(WAITING)則是在等待某一事件的發(fā)生,常見的如等待其他線程的通知或者中斷。
(1)、start方法
是否為static方法:否。
作用:啟動一個新線程,在新線程調(diào)用run方法。
說明:線程調(diào)用start方法,進入運行狀態(tài)(RUNNABLE),但并不意味著線程中的代碼會立即執(zhí)行,因為Java線程中的運行狀態(tài)包含了操作系統(tǒng)層面的【就緒狀態(tài)】和【運行狀態(tài)】,所以只有Java線程真正獲得了CPU的控制權(quán),線程才能真正地在執(zhí)行。每個線程只能調(diào)用一次start方法來啟動線程,如果多次調(diào)用則會出現(xiàn)IllegalThreadStateException。
(2)、run方法
是否為static方法:否。
作用:線程啟動后會調(diào)用的方法。
說明:
①若使用繼承Thread類的方式創(chuàng)建線程,并重寫了run方法,則線程會在啟動后調(diào)用run方法,執(zhí)行其中的代碼。如果繼承時沒有重寫run方法或者run方法中沒有任何代碼,則該線程不會進行任何操作。
②若使用實現(xiàn)Runnable接口的方法創(chuàng)建線程,則在調(diào)用start啟動線程后,也會調(diào)用Runnable實現(xiàn)類中的run方法,如果沒有重寫,則默認不會進行任何操作。
那些run方法和start方法又有什么區(qū)別呢?
③start方法是真正能啟動一個新線程的方法,而run方法則是線程對象中的普通方法,即使線程沒有啟動,也可以通過線程對象來調(diào)用run方法,run方法并不會啟動一個新線程。
代碼如下:
public class StartAndRun{
public static void main(String[] args) {
//使用Thread創(chuàng)建線程
Thread t = new Thread("my_thread"){ //為線程命名為"my_thread"
@Override
public void run() {
//Thread.currentThread().getName():獲取當前線程的名字
System.out.println("【"+Thread.currentThread().getName()+"】"+"線程中的run方法被調(diào)用");
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
}
};
//調(diào)用run方法
t.run();
//調(diào)用start方法
t.start();
}
}
其結(jié)果如下:
【main】線程中的run方法被調(diào)用
0
1
2
【my_thread】線程中的run方法被調(diào)用
0
1
2
可以看出在my_thread線程啟動前(調(diào)用start方法前),也可以調(diào)用線程對象t中的run方法,調(diào)用這個run方法的線程并不會是my_thread線程(因為還沒啟動呢),而是main方法所在的主線程main。這是因為run方法是作為線程對象的普通方法存在的,可以認為run方法中的代碼就是新線程啟動后所需要執(zhí)行的任務。如果通過線程對象調(diào)用run方法,那么在哪個線程調(diào)用的run方法,就由哪個線程負責執(zhí)行。
總的來說,Thread類的對象實例對應著操作系統(tǒng)實際存在的一個線程,該對象實例負責提供給用戶去操作線程、獲取線程信息。start方法會調(diào)用native修飾的本地方法start0,最終在操作系統(tǒng)中啟動一個線程,并會在本地方法中調(diào)用線程對象實例的run方法。所以,調(diào)用run方法并不會啟動一個線程,它只是作為線程對象等著被調(diào)用。
(3)、join方法
是否為static方法:否。
作用:用于同步,可以使用該方法讓線程之間的并行執(zhí)行變?yōu)榇袌?zhí)行。
有代碼如下:
/**
* 主類
*/
public class Join {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
Thread t1 = new Thread(task,"耗子尾汁");
//啟動線程
t1.start();
//主線程打印
for(int i = 0; i < 4; i++){
if (i == 2) {
//join方法:使main線程與t1線程同步執(zhí)行,即t1線程執(zhí)行完,main線程才會繼續(xù)
t1.join();
}
//Thread.currentThread().getName():獲取當前線程的名稱
System.out.println("【"+Thread.currentThread().getName()+"】" + i);
}
}
}
/**
* Runnable接口實現(xiàn)類
*/
class Task implements Runnable{
@Override
public void run() {
for(int i = 0; i < 3; i++){
System.out.println("【"+Thread.currentThread().getName()+"】"+i);
}
}
}
其輸出如下:
【main】0
【main】1
【耗子尾汁】0
【耗子尾汁】1
【耗子尾汁】2
【耗子尾汁】3
【main】2
【main】3
在上面的代碼中,創(chuàng)建了一個命名為“耗子尾汁”的線程,并通過start方法啟動。主線程和“耗子尾汁”線程都有循環(huán)打印i的任務。在“耗子尾汁”線程啟動后,就會進入運行狀態(tài)(Runnable),等待CPU調(diào)度,以獲得CPU使用權(quán)來打印i。而主線程在執(zhí)行“耗子尾汁”線程的start方法后,就會繼續(xù)往下執(zhí)行,循環(huán)打印i。正常來講,主線程和“耗子尾汁”線程應該處于并行執(zhí)行的狀態(tài),即二者會各自執(zhí)行自己的for循環(huán)。但由于在主線程的for循環(huán)中調(diào)用了join方法,使得主線程交出了CPU的控制權(quán),并返回到“耗子尾汁”線程,等待該線程執(zhí)行完畢,主線程才繼續(xù)執(zhí)行。所以join方法就相當于在主線程中同步“耗子尾汁”線程,使“耗子尾汁”線程執(zhí)行完,才會繼續(xù)執(zhí)行主線程。其最終效果就是可以使用該方法讓線程之間的并行執(zhí)行變?yōu)榇袌?zhí)行。
join方法是可以傳參的。join(10)的意思就是,如果在A線程中調(diào)用了B線程.join(10),那么A線程就會同步等待B線程10毫秒,10毫秒后,A、B線程就會并行執(zhí)行。
同時也要注意,只有線程啟動了,調(diào)用join方法才有意義。在上述代碼中,如果“耗子尾汁”線程沒有調(diào)用start方法來啟動,那么join并不會起作用。
(4)、getId方法、getName方法、setName方法
是否為static方法:均為否。
作用:
①getId方法:獲取線程長整型的id、這個線程id是唯一的。
②getName方法:獲取線程名
③setName(String):設置線程名
(5)、getPriority方法、setPriority(int)方法
是否為static方法:均為否。
作用:
①setPriority(int)方法:設置線程的優(yōu)先級,優(yōu)先級的范圍為1-10。
②getPriority方法:獲取線程的優(yōu)先級。
現(xiàn)在的主流操作系統(tǒng)(windows、Linux等)基本都采用了時分的形式來調(diào)度運行線程,即將CPU的時間分為一個個時間片(這些時間片相等的),線程會得到若干時間片,時間片用完就會發(fā)生線程調(diào)度,并等待下一次的分配。線程優(yōu)先級就是決定線程需要多或者少分配一些時間片。
Java線程的優(yōu)先級范圍為1-10,默認優(yōu)先級為5。優(yōu)先級高的線程分配的時間片的數(shù)量要都多于優(yōu)先級低的線程??赏ㄟ^setPriority(int)方法來設置。頻繁阻塞的線程(比如I/O操作或休眠)的線程需要設置較高優(yōu)先級,而計算任務較重(比如偏向運算操作或需要較多CPU時間)的線程則設置較低優(yōu)先級,以避免CPU會被獨占。
需要注意的是,Java線程的優(yōu)先級設置只能給操作系統(tǒng)建議,并不能直接決定線程的調(diào)度,Java線程的調(diào)度只能由操作系統(tǒng)決定。操作系統(tǒng)完全可以忽略Java線程的優(yōu)先級設置。在不同的操作系統(tǒng)上Java線程的優(yōu)先級會存在差異,一些操作系統(tǒng)會直接無視優(yōu)先級的設置。所以一些在邏輯上有先后順序的操作,不能依靠設置Java線程的優(yōu)先級來完成。
Java子線程的默認優(yōu)先級與父線程的優(yōu)先級一致,例如在main方法中創(chuàng)建線程,那么主線程(默認為5)就是這個新線程的父線程,該新線程的默認優(yōu)先級為父線程的優(yōu)先級。如果給主線程設置優(yōu)先級為4,那么這個新線程的默認優(yōu)先級就為4。
(6)、getState()方法、isAlive()方法
是否為static方法:均為否。
作用:
①getState()方法:獲取線程的狀態(tài)(NEW、RUNNABLE、WATING、BLOCKED、TIME_WATING、TERMINATED)
②isAlive()方法:判斷線程是否存活,即是否線程已啟動但尚未終止((還沒有運行完
畢))。
(7)、interrupt()方法
是否為static方法:否。
作用:中斷線程,當A線程運行時,B線程可以通過A線程的對象實例來調(diào)用A線程的interrput()方法設置線程A的中斷標志位true,并立即返回。設置中斷僅僅是設置標志,通過設置中斷標志并不能直接終止該線程的執(zhí)行,而是被中斷的線程根據(jù)中斷狀態(tài)自行處理。如果打斷的是正在運行中的線程,那么該線程就會被設置中斷標志。但如果線程正在執(zhí)行sleep方法或者上面所說的join方法時,被調(diào)用了interrupt方法,那么這個被打斷的線程會拋出出 InterruptedException異常,并清除打斷標志。
(8)、interrupted()方法、isInterrupted()方法
是否為static方法:interrupted為static方法、isInterrupted為非static方法
作用:均為判斷線程是否被打斷。區(qū)別在于interrupted()方法會清除中斷標記,isInterrupted()方法不會清除中斷標志。
說明:在interrupted()方法內(nèi)部是獲取當前調(diào)用線程的中斷標志而不是調(diào)用interrupted()方法的實例對象的中斷標志。
(9)、sleep(long n)方法
是否為static方法:是。
作用:讓線程休眠,當一個執(zhí)行中的線程調(diào)用sleep方法后,該線程就會掛起,并把剩下的CPU時間片交給其他線程,但并不會直接指定由哪個線程占用,需要操作系統(tǒng)來進行調(diào)度。線程在休眠期間不參與CPU調(diào)度,但也不會把線程占有的其他資源(比如鎖)進行釋放。
需要注意的是,休眠時間到后線程也并不會直接繼續(xù)執(zhí)行,而是進入等待CPU調(diào)度的狀態(tài)。同時由于sleep方法是靜態(tài)方法,使用t.sleep()并不會讓t線程進入休眠,而是讓當前線程進入休眠(比如在main方法中調(diào)用t.sleep(),實際上是讓主線程進入休眠)。
(10)、yield() 方法
是否為static方法:是。
作用:使線程讓出CPU控制權(quán)。實際上該方法只是向操作系統(tǒng)請求讓出自己的CPU控制權(quán),但操作系統(tǒng)也可以選擇忽略。線程調(diào)用該方法讓出CPU控制權(quán)后,會進入就緒狀態(tài),也有可能遇到剛讓出CPU控制權(quán)后又被CPU調(diào)度執(zhí)行的情況。