1、執行緒的創建與運行
(1)、繼承或直接使用Thread類
繼承Thread類創建執行緒:
/**
* 主類
*/
public class ThreadTest {
public static void main(String[] args) {
//創建執行緒物件
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類創建執行緒:
class ThreadTest02 {
public static void main(String[] args) {
//直接使用Thread創建執行緒,"My_Thread"是取得執行緒名
Thread my_thread = new Thread("My_Thread"){
@Override
public void run() { //執行緒的任務
System.out.println("My_Thread Running");
}
};
//啟動執行緒
my_thread.start();
}
}
以上的方式都是直接使用Thread類創建執行緒,并通過start方法啟動執行緒,但執行緒并不會立即執行,它還需要等待CPU調度,只有執行緒獲得CPU控制權,才算是真正在執行,
直接使用Thread類的好處是:
方便傳參,可在子類里添加成員變數,通過set方式設定引數或通過建構式傳參
直接使用Thread類的缺點處是:
執行緒的創建和任務代碼冗余在一起,也可能由于繼承了Thread類,故無法再繼承其他類,任務無回傳值,
(2)、使用Runnable介面的run方法
/**
* 主類
*/
public class ThreadTest03 {
public static void main(String[] args) {
RunnableTask task = new RunnableTask();
//創建執行緒,引數1 是任務物件; 引數2 是執行緒名字,推薦寫上
Thread my_thread = new Thread(task,"My_Thread");
//啟動執行緒
my_thread.start();
}
}
/**
* Runable介面實作類
*/
class RunnableTask implements Runnable{
@Override
public void run(){ //執行緒的任務
System.out.println("Thread Running");
}
}
以上的方式是使用Runnable介面的run方法,該方式將任務代碼與執行緒的創建分離,這樣在多個執行緒具有相同任務時,就可以使用同一個Runnable介面實作,同時該方式的Runnable的實作類也可以繼承其他的類,該方式更靈活,故推薦使用其來創建執行緒,
但其缺點也是任務無回傳值,
(3)、使用FutureTask的方式
//創建任務類,類似于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 {
//創建任務物件
FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
//啟動執行緒
new Thread(futureTask,"My_Thread").start();
//主執行緒等待"My_Thread"的任務執行完畢,并回傳結果
String res = futureTask.get();
System.out.println(res);
}
}
上述代碼實作了Callable介面的call()方法,在main函式內首先創建FutureTask物件(建構式為CallerTask的實體),將創建的FutureTask物件作為任務,并放到新創建的執行緒中啟動,運行完畢后,則可以使用get方法等待執行緒里的任務執行完畢并回傳結果,
2、Java執行緒的狀態
Java執行緒在其生命周期中可能有六種狀態,根據Java.lang.Thread類中的列舉型別State的定義,其狀態有以下六種:
①NEW:初始狀態,執行緒已被創建但還未呼叫start()方法來進行啟動,
②RUNNABLE:運行狀態,呼叫start方法后,執行緒處于該狀態,注意,Java執行緒的運行狀態,實際上包含了作業系統中的就緒狀態(已獲得除CPU外的一切運行資源,正在等待CPU調度,獲得CPU控制權)和運行狀態(獲得CPU控制權,執行緒真正在執行),因此,即使Java中的執行緒處于RUNNABLE狀態,也并不意味著該執行緒就一定正在執行(獲得CPU的控制權),該執行緒也有可能在等待CPU調度,
③BLOCKED:阻塞狀態,執行緒阻塞于鎖,即執行緒在鎖的競爭中失敗,則處于阻塞狀態,
④WAITING:等待狀態,該狀態的執行緒需要等待其他執行緒的中斷或通知,
⑤TIME-WAITING:超時等待狀態,該狀態下的執行緒也在等待通知,但若在限定時間內沒有,其他執行緒進行通知,那么超過規定時間的執行緒就會自動“醒來”,繼續執行run方法內的代碼,
⑥TERMINATED:終止狀態,執行緒執行完畢或者執行緒在執行程序中拋出例外,則執行緒結束,執行緒處于終止狀態,

阻塞狀態(BLOCKED),是因為其在鎖競爭中失敗而在等待獲得鎖,而等待狀態(WAITING)則是在等待某一事件的發生,常見的如等待其他執行緒的通知或者中斷,
3、Java執行緒Thread類常用方法
(1)、start方法
是否為static方法:否,
作用:啟動一個新執行緒,在新執行緒呼叫run方法,
說明:執行緒呼叫start方法,進入運行狀態(RUNNABLE),但并不意味著執行緒中的代碼會立即執行,因為Java執行緒中的運行狀態包含了作業系統層面的【就緒狀態】和【運行狀態】,所以只有Java執行緒真正獲得了CPU的控制權,執行緒才能真正地在執行,每個執行緒只能呼叫一次start方法來啟動執行緒,如果多次呼叫則會出現IllegalThreadStateException,
(2)、run方法
是否為static方法:否,
作用:執行緒啟動后會呼叫的方法,
說明:
①若使用繼承Thread類的方式創建執行緒,并重寫了run方法,則執行緒會在啟動后呼叫run方法,執行其中的代碼,如果繼承時沒有重寫run方法或者run方法中沒有任何代碼,則該執行緒不會進行任何操作,
②若使用實作Runnable介面的方法創建執行緒,則在呼叫start啟動執行緒后,也會呼叫Runnable實作類中的run方法,如果沒有重寫,則默認不會進行任何操作,
那些run方法和start方法又有什么區別呢?
③start方法是真正能啟動一個新執行緒的方法,而run方法則是執行緒物件中的普通方法,即使執行緒沒有啟動,也可以通過執行緒物件來呼叫run方法,run方法并不會啟動一個新執行緒,
代碼如下:
public class StartAndRun{
public static void main(String[] args) {
//使用Thread創建執行緒
Thread t = new Thread("my_thread"){ //為執行緒命名為"my_thread"
@Override
public void run() {
//Thread.currentThread().getName():獲取當前執行緒的名字
System.out.println("【"+Thread.currentThread().getName()+"】"+"執行緒中的run方法被呼叫");
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
}
};
//呼叫run方法
t.run();
//呼叫start方法
t.start();
}
}
其結果如下:
【main】執行緒中的run方法被呼叫
0
1
2
【my_thread】執行緒中的run方法被呼叫
0
1
2
可以看出在my_thread執行緒啟動前(呼叫start方法前),也可以呼叫執行緒物件t中的run方法,呼叫這個run方法的執行緒并不會是my_thread執行緒(因為還沒啟動呢),而是main方法所在的主執行緒main,這是因為run方法是作為執行緒物件的普通方法存在的,可以認為run方法中的代碼就是新執行緒啟動后所需要執行的任務,如果通過執行緒物件呼叫run方法,那么在哪個執行緒呼叫的run方法,就由哪個執行緒負責執行,
總的來說,Thread類的物件實體對應著作業系統實際存在的一個執行緒,該物件實體負責提供給用戶去操作執行緒、獲取執行緒資訊,start方法會呼叫native修飾的本地方法start0,最終在作業系統中啟動一個執行緒,并會在本地方法中呼叫執行緒物件實體的run方法,所以,呼叫run方法并不會啟動一個執行緒,它只是作為執行緒物件等著被呼叫,
(3)、join方法
是否為static方法:否,
作用:用于同步,可以使用該方法讓執行緒之間的并行執行變為串行執行,
有代碼如下:
/**
* 主類
*/
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執行緒同步執行,即t1執行緒執行完,main執行緒才會繼續
t1.join();
}
//Thread.currentThread().getName():獲取當前執行緒的名稱
System.out.println("【"+Thread.currentThread().getName()+"】" + i);
}
}
}
/**
* Runnable介面實作類
*/
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
在上面的代碼中,創建了一個命名為“耗子尾汁”的執行緒,并通過start方法啟動,主執行緒和“耗子尾汁”執行緒都有回圈列印i的任務,在“耗子尾汁”執行緒啟動后,就會進入運行狀態(Runnable),等待CPU調度,以獲得CPU使用權來列印i,而主執行緒在執行“耗子尾汁”執行緒的start方法后,就會繼續往下執行,回圈列印i,正常來講,主執行緒和“耗子尾汁”執行緒應該處于并行執行的狀態,即二者會各自執行自己的for回圈,但由于在主執行緒的for回圈中呼叫了join方法,使得主執行緒交出了CPU的控制權,并回傳到“耗子尾汁”執行緒,等待該執行緒執行完畢,主執行緒才繼續執行,所以join方法就相當于在主執行緒中同步“耗子尾汁”執行緒,使“耗子尾汁”執行緒執行完,才會繼續執行主執行緒,其最終效果就是可以使用該方法讓執行緒之間的并行執行變為串行執行,
join方法是可以傳參的,join(10)的意思就是,如果在A執行緒中呼叫了B執行緒.join(10),那么A執行緒就會同步等待B執行緒10毫秒,10毫秒后,A、B執行緒就會并行執行,
同時也要注意,只有執行緒啟動了,呼叫join方法才有意義,在上述代碼中,如果“耗子尾汁”執行緒沒有呼叫start方法來啟動,那么join并不會起作用,
(4)、getId方法、getName方法、setName方法
是否為static方法:均為否,
作用:
①getId方法:獲取執行緒長整型的id、這個執行緒id是唯一的,
②getName方法:獲取執行緒名
③setName(String):設定執行緒名
(5)、getPriority方法、setPriority(int)方法
是否為static方法:均為否,
作用:
①setPriority(int)方法:設定執行緒的優先級,優先級的范圍為1-10,
②getPriority方法:獲取執行緒的優先級,
現在的主流作業系統(windows、Linux等)基本都采用了時分的形式來調度運行執行緒,即將CPU的時間分為一個個時間片(這些時間片相等的),執行緒會得到若干時間片,時間片用完就會發生執行緒調度,并等待下一次的分配,執行緒優先級就是決定執行緒需要多或者少分配一些時間片,
Java執行緒的優先級范圍為1-10,默認優先級為5,優先級高的執行緒分配的時間片的數量要都多于優先級低的執行緒,可通過setPriority(int)方法來設定,頻繁阻塞的執行緒(比如I/O操作或休眠)的執行緒需要設定較高優先級,而計算任務較重(比如偏向運算操作或需要較多CPU時間)的執行緒則設定較低優先級,以避免CPU會被獨占,
需要注意的是,Java執行緒的優先級設定只能給作業系統建議,并不能直接決定執行緒的調度,Java執行緒的調度只能由作業系統決定,作業系統完全可以忽略Java執行緒的優先級設定,在不同的作業系統上Java執行緒的優先級會存在差異,一些作業系統會直接無視優先級的設定,所以一些在邏輯上有先后順序的操作,不能依靠設定Java執行緒的優先級來完成,
Java子執行緒的默認優先級與父執行緒的優先級一致,例如在main方法中創建執行緒,那么主執行緒(默認為5)就是這個新執行緒的父執行緒,該新執行緒的默認優先級為父執行緒的優先級,如果給主執行緒設定優先級為4,那么這個新執行緒的默認優先級就為4,
(6)、getState()方法、isAlive()方法
是否為static方法:均為否,
作用:
①getState()方法:獲取執行緒的狀態(NEW、RUNNABLE、WATING、BLOCKED、TIME_WATING、TERMINATED)
②isAlive()方法:判斷執行緒是否存活,即是否執行緒已啟動但尚未終止((還沒有運行完
畢)),
(7)、interrupt()方法
是否為static方法:否,
作用:中斷執行緒,當A執行緒運行時,B執行緒可以通過A執行緒的物件實體來呼叫A執行緒的interrput()方法設定執行緒A的中斷標志位true,并立即回傳,設定中斷僅僅是設定標志,通過設定中斷標志并不能直接終止該執行緒的執行,而是被中斷的執行緒根據中斷狀態自行處理,如果打斷的是正在運行中的執行緒,那么該執行緒就會被設定中斷標志,但如果執行緒正在執行sleep方法或者上面所說的join方法時,被呼叫了interrupt方法,那么這個被打斷的執行緒會拋出出 InterruptedException例外,并清除打斷標志,
(8)、interrupted()方法、isInterrupted()方法
是否為static方法:interrupted為非static方法、isInterrupted為static方法
作用:均為判斷執行緒是否被打斷,區別在于interrupted()方法不會清除中斷標記,isInterrupted()方法會清除中斷標志,
(9)、sleep(long n)方法
是否為static方法:是,
作用:讓執行緒休眠,當一個執行中的執行緒呼叫sleep方法后,該執行緒就會掛起,并把剩下的CPU時間片交給其他執行緒,但并不會直接指定由哪個執行緒占用,需要作業系統來進行調度,執行緒在休眠期間不參與CPU調度,但也不會把執行緒占有的其他資源(比如鎖)進行釋放,
需要注意的是,休眠時間到后執行緒也并不會直接繼續執行,而是進入等待CPU調度的狀態,同時由于sleep方法是靜態方法,使用t.sleep()并不會讓t執行緒進入休眠,而是讓當前執行緒進入休眠(比如在main方法中呼叫t.sleep(),實際上是讓主執行緒進入休眠),
(10)、yield() 方法
是否為static方法:是,
作用:使執行緒讓出CPU控制權,實際上該方法只是向作業系統請求讓出自己的CPU控制權,但作業系統也可以選擇忽略,執行緒呼叫該方法讓出CPU控制權后,會進入就緒狀態,也有可能遇到剛讓出CPU控制權后又被CPU調度執行的情況,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/240339.html
標籤:Java
上一篇:java多重繼承
