注意: 原文鏈接:https://blog.csdn.net/duanduan_l/java/article/details/86505843
創建線程
1、繼承Thread類創建線程類
步驟: (1)創建Thread類的子類,並重寫run方法,run方法的方法體代表該線程需要完成的任務;
(2)創建Thread類的實例,即創建線程對象;
(3)調用線程對象的start()方法啟動線程;
示例如下:
public class Thread1208 extends Thread{ @Override public void run() { //重寫run方法 System.out.println("創建線程1"); } } public class Test{ public static void main(String[] args){ /** * 繼承Thread創建線程(該方法無法實現多個線程共享線程類的實例變量) */ Thread1208 t = new Thread1208(); t.start(); } }
2、實現Runnable接口,創建線程
步驟: (1)定義Runnable接口的實現類,並重寫run ()方法;
(2)創建實現類的實例對象,將此實例作為Thread類的target創建Thread對象,該Thread對象才是真正的線程對象;
(3)調用start()方法啟動線程。
示例如下:
public class MyThread immplements Runnable { public void run(){ system.out.println("MyThread.run()"); } } public class Test{ public static void main(String[] args){ MyThread runThread = new MyThread(); Thread runThread = new Thread(runThread,""); runThread.start(); } }
3、實現Callable接口,通過FutureTask包裝器創建線程
步驟: (1)創建Callable接口的實現類,重寫call()方法,call()方法的執行體也是執行該線程任務,且call()方法有返回值;
(2)使用FutureTask類包裝Callable對象,該FutureTask封裝類Callable的call()方法的返回值;
(3)使用FutureTask對象作為Thread類的target創建線程;
(4)調用線程的start()方法啟動線程。
示例如下:
public class Callable1208 implements Callable <Integer>{ int i = 0; @Override public Integer call() throws Exception { for(;i < 20;i++){ System.out.println(Thread.currentThread().getName()+" "+i); } return i; } } public class Test{ public static void main(String[] args){ /** * 通過實現Callable接口創建線程(將Callable實現類作為Thread的target,並接受線程體的返回值) */ Callable1208 target = new Callable1208(); FutureTask<Integer> f = new FutureTask<Integer>(target); Thread thread = new Thread(f,"新線程"); thread.start(); try { System.out.println(f.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
結論:采取Runnable、Callable的優勢在於——線程類只是實現了Runnable或Callable接口,還可以繼承其它類;在這種方法下,多個線程可以共享一個target對象,因此非常適合多個相同線程處理同一份資源的情況,從而將CPU、代碼和數據分開,形參清晰的模型,體現了面對對象的編程思想。劣勢在於編程復雜度略高。
三種實現方式的區別:
繼承Thread類為單繼承,實現Runable接口為多實現,靈活性上,實現Runnable接口創建線程更靈活;
通過實現Runnable接口創建線程可以實現多線程內的資源共享;
通過實現Runnable接口創建線程增強代碼的健壯性,代碼可以被多個線程共享,代碼和數據獨立;
線程池只能放Runnale或Callable實現類創建的線程,不能存放繼承Thread類創建的線程;
多線程操作的方法
1、start()方法
用於啟動一個線程,使相應的線程進入排隊等待狀態。一旦其獲得CPU資源,就可以脫離它的主線程獨立開始自己的生命周期。也就是說,一個線程即使調用了start()方法,也不一定會立即執行。start()方法要首先調用,不能重復調用。
2、run()方法
Thread類中的run()方法和Runnable接口中的run()方法作用相同,單獨調用run()方法,會在當前線程執行run()方法,而並不會啟動新線程。
3、sleep()方法
線程睡眠,該方法是Thread類的靜態方法,作用是:讓當前線程休眠指定時間,時間到達后自動恢復線程執行,該方法不會釋放對象鎖,即若有synchronized代碼塊,其他線程仍不能訪問共享數據,該方法要捕捉異常。
4、yield()方法
線程讓步,該方法與sleep()方法類似,也是Thread類的靜態方法(獲取當前正在執行的程序,通過Thread實例對象不一定正在執行,不一定正在使用cpu使用權),讓正在執行的程序讓步(讓出cpu使用權),讓給優先級較高的線程執行。(如果沒有優先級較高的線程,當前程序會再次獲得cpu的使用權);該方法不能由用戶指定暫停時間,且yield()方法只能讓同優先級的線程有執行機會。
5、join()方法
該方法使調用該方法的線程在此之前執行完畢,即等待該方法的線程執行完畢后再向下繼續執行。該方法也捕捉異常,該方法的影響只存在於執行join()方法的線程和調用該線程的線程之間。
6、interrupt()方法
Thread中的stop()和suspend()方法,由於固有的不安全性,已經建議不再使用!
interrupt()的作用是中斷當前線程。終止處於“阻塞狀態”的線程,當線程由於被調用了sleep(), wait(), join()等方法而進入等待狀態;若此時調用線程的interrupt()將線程的中斷標記設為true。由於處於阻塞狀態,中斷標記會被清除,同時產生一個InterruptedException異常,將InterruptedException放在適當的位置就能終止線程;終止處於“運行狀態”的線程interrupt()並不會終止處於“運行狀態”的線程!它會將線程的中斷標記設為true。
7、setDeamon(boolean b)與isDeamon()方法
首先介紹一下守護線程的含義,守護線程為脫離於終端控制的線程,服務於非守護線程 -->java --> JVM實例 -->gc --->守護線程,守護線程生命周期,有非守護線程存在,那么守護線程存在,否則,守護線程也結束;setDeamon(boolean b)方法用於設置線程為守護線程 ,false:非守護線程(默認),true:守護線程;isDeamon();//判斷當前這個線程是否為守護線程 返回值為Boolean類型,返回 true:守護線程,反之為非守護線程。
setPriority()方法
setPriority()方法用於設置線程優先級,來讓線程具有較高的執行權限(1-10,默認5);
創建3個線程,然后倒序執行;
public class Test{ public static void main(String[] args){ /** *創建3個線程,倒敘啟動 */ Thread t3 = new Thread(new Runnable() { @Override public void run() { System.out.println("t3"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { try { t3.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t2"); } }); Thread t1 = new Thread(new Runnable() { @Override public void run() { try { t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("t1"); } }); t1.start(); t2.start(); t3.start(); } }
線程狀態
線程狀態可以有以下5種狀態:
1、New //新建狀態
用new語句創建的線程處於新建狀態,此時它和其他Java對象一樣,僅僅在堆區中被分配了內存。
2、Runnable //可執行狀態
可執行狀態又分為ready(就緒狀態)和Running(執行狀態)。
當一個線程對象創建后,其他線程調用它的start()方法,該線程就進入就緒狀態,Java虛擬機會為它創建方法調用棧和程序計數器。處於這個狀態的線程位於可運行池中,等待獲得CPU的使用權(等待cpu分配執行時間)。
Running(運行狀態),處於這個狀態的線程占用CPU,執行程序代碼。只有處於就緒狀態的線程才有機會轉到運行狀態。
4、Blocked (阻塞狀態) 是指線程因為某些原因放棄CPU,暫時停止運行。當線程處於阻塞狀態時,Java虛擬機不會給線程分配CPU。直到線程重新進入就緒狀態,它才有機會轉到運行狀態。
5、Waiting //等待狀態
當線程等待另一個線程通知調度器一個條件時,它自己進入等待狀態。在調用Object.wait方法或Thread.join方法,或者等待Lock或Condition時,就會出現這種狀況。
6、Timed waiting //超時等待狀態
有幾個方法有超時參數,調用它們導致線程進入即使等待狀態。這一狀態將一直保持到超時期滿或收到適當的通知。
7、Terminated //終止狀態
當線程退出run()方法時,就進入死亡狀態,該線程結束生命周期。
線程狀態轉換
下圖展示線程可以具有的狀態以及從一個狀態到另一個狀態可能的轉換。
1、調整現場優先級:Java線程有優先級,優先級高的線程獲得較多的運行機會(運行時間);
static int Max_priority 線程可以具有的最高優先級,值為10;
static int MIN_PRIORIYT 線程可以具有的最低優先級,值為1;
static int NORM_PRIORITY 分配給線程的默認優先級,值為5;
Thread類的setPriority()和getPriority()方法分別用來設置和獲取線程的優先級;
2、線程睡眠:Thread.sleep(long millins)使線程轉到TimeWaiting狀態;
3、線程等待:Object.wait()方法,釋放線程鎖,使線程進入無期限等待狀態,直到被其他線程喚醒(notify()和notifyAll());
4、線程讓步:Thread.yeild()方法暫停當前正在執行(Running)的線程,使其進入等待執行(ready)狀態, 把執行機會讓給相同優先級或更高優先級的線程,如果沒有較高優先級或相同優先級的線程, 該線程會繼續執行;
5、線程加入:join()方法,在當前線程中調用另一個線程的join()方法,則當前線程轉入等待(waiting)狀態,知道另一個進程運行結束,當前線程再有等待狀態轉為就緒狀態;
問題解決
1、yield,sleep,join方法區別??
sleep()方法
線程的方法,sleep()方法需要指定時間,讓正在執行的線程在指定時間暫停執行,進入阻塞狀態;該方法可以使(同)高優先級獲得執行機會,也可以使低優先級的線程獲得執行機會,但該方法不能釋放鎖標志,即如果有synchronized代碼塊,其他線程仍然不能訪問共享數據;
wait()方法
實例方法,用於鎖機制中,wait()方法一般是和notify()和notifyAll()方法一起使用,這三個方法用於多個線程對共享數據的存取,必須在synchronized代碼塊中使用,這三個方法都是Onject類的方法;
與sleep()方法區別在於:wait()方法會釋放鎖標志,在調用某一對象的wait()方法后,會暫停線程執行,並將當前線程放入對象等待池中,直到調用notify()方法,將從對象等待池中移出任意一個線程放入鎖標志等待池,只有鎖標志等待池中的線程可以獲取鎖標志,隨時爭奪鎖的擁有權,而調用notifyAll()方法,將從對象等待池中移出所有的線程到鎖標志等待池中。除使用notify和notifyAll方法,也可以使用指定時間的wait方法;
由於這三個方法只能在synchronized代碼塊中執行,若使用重入鎖要實現相同的效果,可以調用重入鎖的newCondition()創建對象,調用await(),signal()和signalAll()方法;
yield()
該方法不會釋放鎖標志,與sleep()方法的區別在於:沒有參數,該方法可以使當前線程重新回到可執行狀態,只能使同優先級或高優先級的線程得到執行機會;
join()
該方法會使得當前線程在調用join()方法的線程執行完再執行;
2、繼承Thread類與實現Runnable接口創建線程的區別:
(1)繼承Thread類為單繼承,實現Runable接口為多實現,靈活性上,實現Runnable接口創建線程更靈活;
(2)線程類繼承自Thread相對於Runable來說,使用線程的方法更方便一些;
(3)實現Runnable接口的線程類的多個線程、可以更方便的訪問同一個變量,而Thread類需要內部類來進行替換.
3、 實現Runnable接口與實現Callable接口創建線程的區別:
(1)Callable接口的實現類重寫call()方法,有返回值,而Runnable接口實現類重寫run()方法沒有返回值;
(2)call()方法可拋出異常,而run()方法是不能拋出異常的;
(3)運行Callable任務可拿到一個Future對象, Future表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。通過Future對象可了解任務執行情況,可取消任務的執行,還可獲取任務執行的結果。