想學(xué)習(xí)技術(shù)一般是先知其然然后知其所以然。所以下面會討論到簡單的使用丈氓。
main線程
一個進(jìn)程正在運行時周循,至少會有一個線程在運行。Java也是這樣万俗。比如我們啟動一個Java項目湾笛,會調(diào)用mian方法,調(diào)用main方法的線程就是這樣闰歪,而且它是由JVM創(chuàng)建的嚎研。例如:
在控制臺輸出的main其實就是一個名叫mian的線程在執(zhí)行main方法中的代碼,另外课竣,控制臺輸出的main和main方法本身沒有任何關(guān)系嘉赎,僅僅是名字相同而已。因為我們可以為線程設(shè)置個性化名字于樟。而且main方法也可以調(diào)用線程執(zhí)行其他代碼公条。.上面只是簡單看了一個mian方法的線程名字,下面介紹Java幾種常見的線程實現(xiàn)方式迂曲。
繼承Thread類
在JDK中已經(jīng)自帶了對多線程技術(shù)的支持靶橱。可以很方便進(jìn)行多線程編程。下面看實現(xiàn)多線程的第一種方式关霸,繼承Thread類传黄。
Thread類位于java.lang包中,是Java1.0就有的類队寇。先看一下類的定義:
可以看到類繼承了Runnable接口膘掰,它們之間是有多態(tài)關(guān)系的,這個接口后面會講到佳遣。我們先寫一個小例子看看用法识埋。
上面的類繼承了Thread,覆蓋了一個run方法零渐,并在其中輸出了一行文字窒舟,在main方法中,調(diào)用start方法執(zhí)行了這個線程诵盼。從輸出可以看出惠豺,run方法執(zhí)行的比較晚,這樣說明在使用多線程技術(shù)時风宁,代碼運行結(jié)果與代碼執(zhí)行或者調(diào)用順序是無關(guān)的洁墙。
線程是一個子任務(wù),cup以不確定的方式杀糯,或者說隨機(jī)的時間來調(diào)用線程中的run方法扫俺,所以先打印結(jié)束在打印名字。
注意固翰,線程結(jié)束后不能再次調(diào)用線程狼纬,否則會出現(xiàn)異常:java.lang.IllegalThreadStateException
下面通過一個更加直觀的例子演示線程的隨機(jī)性。
可以看到是個線程的結(jié)果并不是按照0到9的順序執(zhí)行的骂际,與代碼的順序無關(guān)疗琉。同樣可以看出,調(diào)用start方法的順序也不代表線程的啟動順序歉铝。
在Thread類中盈简,start()方法通知“線程規(guī)劃器”此線程已經(jīng)準(zhǔn)備就緒,等待調(diào)用線程對象的run方法太示,這個過程其實就是讓系統(tǒng)安排一個時間來調(diào)用Thread中的run()方法柠贤,使線程得到運行,因此啟動線程类缤,具有異步的效果臼勉。很多人在使用Thread的時候回調(diào)用run方法執(zhí)行,如果調(diào)用run方法就是僅僅調(diào)用了一個方法而已餐弱,并不是異步宴霸,此線程對象并不交給線程規(guī)劃器處理囱晴。而是由主線程main調(diào)用run方法執(zhí)行,是同步操作瓢谢,這也是run和start的區(qū)別畸写。
實現(xiàn)Runnable接口
使用Thread類最大的局限就是實現(xiàn)方式,因為Java是單繼承氓扛,如果去繼承Thread類枯芬,那么就不能再去繼承其它類實現(xiàn)具體業(yè)務(wù) ,多以完全可以使用實現(xiàn)Runnable接口的方式實現(xiàn)多線程采郎。Thread類本身也是實現(xiàn)了Runnable接口破停,所以這兩種方式創(chuàng)建的線程在工作時性質(zhì)是一樣的。下面先看一下Runnable接口的源碼:
類里面只有一個run方法尉剩,是很多多線程類的基礎(chǔ)類。Runnable接口也在java.lang包下面毅臊,是從jdk1.0就開始有的基礎(chǔ)類理茎。下面看一個實現(xiàn)此接口的小例子:
如何使用Runnable呢?這就要看一下Thread類的構(gòu)造函數(shù)了:
在Thread類的9個構(gòu)造函數(shù)中管嬉,有兩個分別是 Thread(Runnable target)和Thread(Runnable target, String name)皂林,這兩個可以傳遞Runnable接口,可以想到使用方法如下:
從運行結(jié)果可以看到?jīng)]有什么特殊之處蚯撩。傳入的r2也是自定義了線程的名字础倍。另外需要說明的是,Thread類也實現(xiàn)了Runnable接口胎挎,所以這兩個構(gòu)造函數(shù)不見可以傳入Runnable接口沟启,還可以傳入一個Thread實例對象,這樣就實現(xiàn)了一個線程的run方法交由另一個線程去調(diào)用運行犹菇。
實現(xiàn)Callable<V>接口
Callable接口是JDK1.5之后有的線程實現(xiàn)方式德迹。我們先看一下源碼:
這個類位于java.util.concurrent包中,也即是常說的juc中揭芍。接口名稱中帶著泛型胳搞,call方法的返回類型也是泛型,說明這是一個可以有返回值的多線程實現(xiàn)方式称杨,這是與前面兩種實現(xiàn)的本質(zhì)上的區(qū)別肌毅。下面先看一個例子:
可以看到,接口的實現(xiàn)本質(zhì)上沒什么區(qū)別姑原。那么這個接口怎么運行呢悬而?顯然通過Thread類的構(gòu)造方法可以看到,不能使用Thread類页衙。Runnable接口有對應(yīng)的實現(xiàn)類Thread摊滔,可以想到Callable接口應(yīng)該也有一個阴绢,沒錯,就是FutureTask類艰躺。先看一下調(diào)用方法:
從運行結(jié)果可以看到成功獲取了線程的運行結(jié)果呻袭。獲取線程返回值的多線程實現(xiàn)方式比上面的Thread和Runnable方式要復(fù)雜一些,包含了JDK1.6以后新增的幾個接口和類腺兴,當(dāng)然Callable是基礎(chǔ)左电。下面一個一個介紹。
首先看一下Future接口:
這個接口是1.5以后出現(xiàn)的页响,可以傳入泛型篓足,用來做什么呢?那就要看一下接口中定義的幾個方法:
可以看到接口中包含了五個方法cancel闰蚕,isCancelled栈拖,isDone,get没陡,get(long涩哟,TimeUnit),弄明白這五個方法的意思大概也就知道這個接口的作用盼玄。
Future就是對于具體的Runnable或者Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消贴彼、查詢是否完成、獲取結(jié)果埃儿。必要時可以通過get方法獲取執(zhí)行結(jié)果器仗,該方法會阻塞直到任務(wù)返回結(jié)果。這是定義Future接口的作用童番。五個方法說明如下:
cancel方法用來取消任務(wù)精钮,如果取消任務(wù)成功則返回true,如果取消任務(wù)失敗則返回false妓盲。參數(shù)mayInterruptIfRunning表示是否允許取消正在執(zhí)行卻沒有執(zhí)行完畢的任務(wù)杂拨,如果設(shè)置true,則表示可以取消正在執(zhí)行過程中的任務(wù)悯衬。如果任務(wù)已經(jīng)完成弹沽,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false筋粗,即如果取消已經(jīng)完成的任務(wù)會返回false策橘;如果任務(wù)正在執(zhí)行,若mayInterruptIfRunning設(shè)置為true娜亿,則返回true丽已,若mayInterruptIfRunning設(shè)置為false,則返回false买决;如果任務(wù)還沒有執(zhí)行沛婴,則無論mayInterruptIfRunning為true還是false吼畏,肯定返回true。
isCancelled方法表示任務(wù)是否被取消成功嘁灯,如果在任務(wù)正常完成前被取消成功泻蚊,則返回 true。
isDone方法表示任務(wù)是否已經(jīng)完成丑婿,若任務(wù)完成性雄,則返回true;
get()方法用來獲取執(zhí)行結(jié)果羹奉,這個方法會產(chǎn)生阻塞秒旋,會一直等到任務(wù)執(zhí)行完畢才返回;
get(long timeout, TimeUnit unit)用來獲取執(zhí)行結(jié)果诀拭,如果在指定時間內(nèi)迁筛,還沒獲取到結(jié)果,就直接返回null耕挨。
也就是說Future提供了三種功能:
1)判斷任務(wù)是否完成瑰煎;
2)能夠中斷任務(wù);
3)能夠獲取任務(wù)執(zhí)行結(jié)果俗孝。
因為Future只是一個接口,所以是無法直接用來創(chuàng)建對象使用的魄健,因此就有了的FutureTask赋铝。
在看FutureTask之前,要先看一個接口RunnableFuture:
這個接口就比較簡單了沽瘦,繼承了兩個接觸接口革骨,并定義了一個run方法,RunnableFuture?繼承了Runnable析恋, Future良哲,表示可以帶有返回值的run接口。
看完上面兩個接口的結(jié)構(gòu)助隧,最后看看FutureTask類:
從1.5開始有的類筑凫,實現(xiàn)了RunnableFuture,再看一下方法:
可以看到并村,這個類的方法大部分是實現(xiàn)了上面幾個接口的方法巍实,并對線程的結(jié)果加了處理方法。現(xiàn)在可以總結(jié)出哩牍,RunnableFuture繼承了Runnable接口和Future接口棚潦,而FutureTask實現(xiàn)了RunnableFuture接口。所以它既可以作為Runnable被線程執(zhí)行膝昆,又可以作為Future得到Callable的返回值丸边。這就是帶有返回值的多線程的實現(xiàn)方式叠必。這幾個新增的類都在juc包中。為什么可以獲取返回值妹窖,后面會詳細(xì)分析源碼纬朝。? ? ?
上面介紹了JDK中三種多線程的實現(xiàn)方式,已經(jīng)底層接口和類的繼承實現(xiàn)關(guān)系嘱吗,只是三個使用的小例子玄组。
代碼地址:https://gitee.com/blueses/java-threads-demo? 01? 02? 03
我們的交流基地,“JAVA互聯(lián)網(wǎng)技術(shù)交流:789650498”歡迎小伙伴們一起來交流: