Java 创建线程的三种方式
一、繼承Thread類創(chuàng)建
1、定義一個類繼承Thread類,并重寫Thread類的run()方法,run()方法的方法體就是線程要完成的任務(wù),因此把run()稱為線程的執(zhí)行體;
2、創(chuàng)建該類的實例對象,即創(chuàng)建了線程對象;
3、調(diào)用線程對象的start()方法來啟動線程;
public class ExtendThread extends Thread {private int i;public static void main(String[] args) {for(int j = 0;j < 50;j++) {//調(diào)用Thread類的currentThread()方法獲取當(dāng)前線程System.out.println(Thread.currentThread().getName() + " " + j);if(j == 10) {//創(chuàng)建并啟動第一個線程new ExtendThread().start();//創(chuàng)建并啟動第二個線程new ExtendThread().start();}}}public void run() {for(;i < 100;i++) {//當(dāng)通過繼承Thread類的方式實現(xiàn)多線程時,可以直接使用this獲取當(dāng)前執(zhí)行的線程System.out.println(this.getName() + " " + i);}} }代碼相關(guān):
1、上述的getName()方法是返回當(dāng)前線程的名字,也可以通過setName()方法設(shè)置當(dāng)前線程的名字;
2、當(dāng)JAVA程序運行后,程序至少會創(chuàng)建一個主線程(自動),主線程的線程執(zhí)行體不是由run()方法確定的,而是由main()方法確定的;
3、在默認(rèn)情況下,主線程的線程名字為main,用戶創(chuàng)建的線程依次為Thread—1、Thread—2、....、Thread—3;
代碼分析:
... Thread-1 97 Thread-1 98 Thread-1 99 main 45 main 46 main 47 main 48 main 49 Thread-0 41 Thread-0 42 Thread-0 43 Thread-0 44 ...這是代碼運行后的截圖,從圖中可以看出:
1、有三個線程:main、Thread-0 、Thread-1
2、Thread-0 、Thread-1兩個線程輸出的成員變量 i?的值不連續(xù)(這里的 i?是實例變量而不是局部變量)。因為:通過繼承Thread類實現(xiàn)多線程時,每個線程的創(chuàng)建都要創(chuàng)建不同的子類對象,導(dǎo)致Thread-0 、Thread-1兩個線程不能共享成員變量 i?;
3、線程的執(zhí)行是搶占式,并沒有說Thread-0?或者Thread-1一直占用CPU(這也與線程優(yōu)先級有關(guān),這里Thread-0 、Thread-1線程優(yōu)先級相同,關(guān)于線程優(yōu)先級的知識這里不做展開);
?
二、 通過Runnable接口創(chuàng)建線程類
1、定義一個類實現(xiàn)Runnable接口;
2、創(chuàng)建該類的實例對象obj;
3、將obj作為構(gòu)造器參數(shù)傳入Thread類實例對象,這個對象才是真正的線程對象;
4、調(diào)用線程對象的start()方法啟動該線程;
public class ImpRunnable implements Runnable { private int i;@Overridepublic void run() {for(;i < 50;i++) { //當(dāng)線程類實現(xiàn)Runnable接口時,要獲取當(dāng)前線程對象只有通過Thread.currentThread()獲取System.out.println(Thread.currentThread().getName() + " " + i);}}public static void main(String[] args) {for(int j = 0;j < 30;j++) {System.out.println(Thread.currentThread().getName() + " " + j);if(j == 10) {ImpRunnable thread_target = new ImpRunnable();//通過new Thread(target,name)的方式創(chuàng)建線程new Thread(thread_target,"線程1").start();new Thread(thread_target,"線程2").start();} }} }代碼相關(guān):
1、實現(xiàn)Runnable接口的類的實例對象僅僅作為Thread對象的target,Runnable實現(xiàn)類里包含的run()方法僅僅作為線程執(zhí)行體,而實際的線程對象依然是Thread實例,這里的Thread實例負責(zé)執(zhí)行其target的run()方法;
2、通過實現(xiàn)Runnable接口來實現(xiàn)多線程時,要獲取當(dāng)前線程對象只能通過Thread.currentThread()方法,而不能通過this關(guān)鍵字獲取;
3、從JAVA8開始,Runnable接口使用了@FunctionlInterface修飾,也就是說Runnable接口是函數(shù)式接口,可使用lambda表達式創(chuàng)建對象,使用lambda表達式就可以不像上述代碼一樣還要創(chuàng)建一個實現(xiàn)Runnable接口的類,然后再創(chuàng)建類的實例。
代碼分析:
... main 12 main 13 線程1 0 線程2 0 main 14 線程2 2 線程1 1 線程2 3 main 15 線程2 5 線程2 6 ...這是代碼運行后的結(jié)果截圖,從圖中可以看出:
1、線程1和線程2輸出的成員變量i是連續(xù)的,也就是說通過這種方式創(chuàng)建線程,可以使多線程共享線程類的實例變量,因為這里的多個線程都使用了同一個target實例變量。但是,當(dāng)你使用我上述的代碼運行的時候,你會發(fā)現(xiàn),其實結(jié)果有些并不連續(xù),這是因為多個線程訪問同一資源時,如果資源沒有加鎖,那么會出現(xiàn)線程安全問題(這是線程同步的知識,這里不展開);
?
三、 使用Callable和Future創(chuàng)建線程
通過這兩個接口創(chuàng)建線程,你要知道這兩個接口的作用,下面我們就來了解這兩個接口:通過實現(xiàn)Runnable接口創(chuàng)建多線程時,Thread類的作用就是把run()方法包裝成線程的執(zhí)行體,那么,是否可以直接把任意方法都包裝成線程的執(zhí)行體呢?從JAVA5開始,JAVA提供提供了Callable接口,該接口是Runnable接口的增強版,Callable接口提供了一個call()方法可以作為線程執(zhí)行體,但call()方法比run()方法功能更強大,call()方法的功能的強大體現(xiàn)在:
1、call()方法可以有返回值;
2、call()方法可以聲明拋出異常;
從這里可以看出,完全可以提供一個Callable對象作為Thread的target,而該線程的線程執(zhí)行體就是call()方法。但問題是:Callable接口是JAVA新增的接口,而且它不是Runnable接口的子接口,所以Callable對象不能直接作為Thread的target。還有一個原因就是:call()方法有返回值,call()方法不是直接調(diào)用,而是作為線程執(zhí)行體被調(diào)用的,所以這里涉及獲取call()方法返回值的問題。
于是,JAVA5提供了Future接口來代表Callable接口里call()方法的返回值,并為Future接口提供了一個FutureTask實現(xiàn)類,該類實現(xiàn)了Future接口,并實現(xiàn)了Runnable接口,所以FutureTask可以作為Thread類的target,同時也解決了Callable對象不能作為Thread類的target這一問題。
?在Future接口里定義了如下幾個公共方法來控制與它關(guān)聯(lián)的Callable任務(wù):
1、boolean cancel(boolean mayInterruptIfRunning):試圖取消Future里關(guān)聯(lián)的Callable任務(wù);
2、V get():返回Callable任務(wù)里call()方法的返回值,調(diào)用該方法將導(dǎo)致程序阻塞,必須等到子線程結(jié)束以后才會得到返回值;
3、V get(long timeout, TimeUnit unit):返回Callable任務(wù)里call()方法的返回值。該方法讓程序最多阻塞timeout和unit指定的時間,如果經(jīng)過指定時間后,Callable任務(wù)依然沒有返回值,將會拋出TimeoutException異常;
4、boolean isCancelled():如果Callable任務(wù)正常完成前被取消,則返回true;
5、boolean isDone():如果Callable任務(wù)已經(jīng)完成,?則返回true;
這種方式創(chuàng)建并啟動多線程的步驟如下:
1、創(chuàng)建Callable接口實現(xiàn)類,并實現(xiàn)call()方法,該方法將作為線程執(zhí)行體,且該方法有返回值,再創(chuàng)建Callable實現(xiàn)類的實例;
2、使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值;
3、使用FutureTask對象作為Thread對象的target創(chuàng)建并啟動新線程;
4、調(diào)用FutureTask對象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值。
public class ThirdThreadImp {public static void main(String[] args) {//這里call()方法的重寫是采用lambda表達式,沒有新建一個Callable接口的實現(xiàn)類FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{int i = 0;for(;i < 50;i++) {System.out.println(Thread.currentThread().getName() + " 的線程執(zhí)行體內(nèi)的循環(huán)變量i的值為:" + i); }//call()方法的返回值return i;});for(int j = 0;j < 50;j++) {System.out.println(Thread.currentThread().getName() + " 大循環(huán)的循環(huán)變量j的值為:" + j);if(j == 20) {new Thread(task,"有返回值的線程").start();}}try {System.out.println("子線程的返回值:" + task.get());} catch (Exception e) {e.printStackTrace();}} }代碼相關(guān):(看不懂)
1、上述代碼沒有使用創(chuàng)建一個實現(xiàn)Callable接口的類,然后創(chuàng)建一個實現(xiàn)類實例的做法。因為,從JAVA8開始可以直接使用Lambda表達式創(chuàng)建Callable對象,所以上面的代碼使用了Lambda表達式;
2、call()方法的返回值類型與創(chuàng)建FutureTask對象時<>里的類型一致。
代碼分析:
這里就不過多的分析,只看最后一行輸出可知:調(diào)用FutureTask對象的get()方法,必須等到子線程結(jié)束以后,才會有返回值。
?
三種創(chuàng)建方式對比
? ? ?上面已經(jīng)介紹完了JAVA中創(chuàng)建線程的三種方法,通過對比我們可以知道,JAVA實現(xiàn)多線程可以分為兩類:一類是繼承Thread類實現(xiàn)多線程;另一類是:通過實現(xiàn)Runnable接口或者Callable接口實現(xiàn)多線程。
下面我們來分析一下這兩類實現(xiàn)多線程的方式的優(yōu)劣:
通過繼承Thread類實現(xiàn)多線程:
優(yōu)點:
1、實現(xiàn)起來簡單,而且要獲取當(dāng)前線程,無需調(diào)用Thread.currentThread()方法,直接使用this即可獲取當(dāng)前線程;
缺點:
1、線程類已經(jīng)繼承Thread類了,就不能再繼承其他類;
2、多個線程不能共享同一份資源(如前面分析的成員變量 i );
通過實現(xiàn)Runnable接口或者Callable接口實現(xiàn)多線程:
優(yōu)點:
1、線程類只是實現(xiàn)了接口,還可以繼承其他類;
2、多個線程可以使用同一個target對象,適合多個線程處理同一份資源的情況。
缺點:
1、通過這種方式實現(xiàn)多線程,相較于第一類方式,編程較復(fù)雜;
2、要訪問當(dāng)前線程,必須調(diào)用Thread.currentThread()方法。
綜上:
一般采用第二類方式實現(xiàn)多線程。
總結(jié)
以上是生活随笔為你收集整理的Java 创建线程的三种方式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python爬虫之xpath的详细使用(
- 下一篇: Java 时间处理(格式解释、格式化时间