一、使用線程
有三種使用線程的方法:
* 實現(xiàn) Runnable 接口贞奋;
* 實現(xiàn) Callable 接口;
* 繼承 Thread 類。
實現(xiàn) Runnable 和Callable 接口的類只能當做一個可以在線程中運行的任務糖荒,不是真正意義上的線程,因此最后還需要通過 Thread來調用模捂〈范洌可以說任務是通過線程驅動從而執(zhí)行的。
1狂男、實現(xiàn) Runnable 接口
需要實現(xiàn) run() 方法综看。
通過 Thread 調用start() 方法來啟動線程。
public class MyRunnable implements Runnable {
??? public void run() {
??????? // ...
??? }
}
public static void main(String[] args) {
??? MyRunnable instance = new MyRunnable();
??? Thread thread = new Thread(instance);
??? thread.start();
}
2岖食、實現(xiàn) Callable 接口
與 Runnable 相比红碑,Callable可以有返回值,返回值通過FutureTask 進行封裝泡垃。
public class MyCallable implements Callable {
??? public Integer call() {
??????? return 123;
??? }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
??? MyCallable mc = new MyCallable();
??? FutureTask ft = newFutureTask<>(mc);
??? Thread thread = new Thread(ft);
??? thread.start();
??? System.out.println(ft.get());
}
Callable與Runnable
???? 先說一下java.lang.Runnable吧析珊,它是一個接口,在它里面只聲明了一個run()方法:
public interface Runnable {
????? public abstract void run();
}
由于run()方法返回值為void類型兔毙,所以在執(zhí)行完任務之后無法返回任何結果唾琼。
Callable位于java.util.concurrent包下,它也是一個接口澎剥,在它里面也只聲明了一個方法锡溯,只不過這個方法叫做call():
public interface Callable {
??? /**
???? * Computes a result, orthrows an exception if unable to do so.
???? *
???? * @return computedresult
???? * @throws Exception ifunable to compute a result
???? */
??? V call() throwsException;
}
可以看到赶舆,這是一個泛型接口,call()函數(shù)返回的類型就是傳遞進來的V類型祭饭。
那么怎么使用Callable呢芜茵?一般情況下是配合ExecutorService來使用的,在ExecutorService接口中聲明了若干個submit方法的重載版本:
Future submit(Callable task);
Future submit(Runnable task, T result);
Future submit(Runnable task);
Future
Future就是對于具體的Runnable或者Callable任務的執(zhí)行結果進行取消倡蝙、查詢是否完成九串、獲取結果。必要時可以通過get方法獲取執(zhí)行結果寺鸥,該方法會阻塞直到任務返回結果猪钮。
Future類位于java.util.concurrent包下,它是一個接口胆建,在Future接口中聲明了5個方法烤低,下面依次解釋每個方法的作用:
cancel方法用來取消任務,如果取消任務成功則返回true笆载,如果取消任務失敗則返回false扑馁。
isCancelled方法表示任務是否被取消成功,如果在任務正常完成前被取消成功凉驻,則返回 true腻要。
isDone方法表示任務是否已經完成,若任務完成涝登,則返回true雄家;
get()方法用來獲取執(zhí)行結果,這個方法會產生阻塞缀拭,會一直等到任務執(zhí)行完畢才返回咳短;get(long timeout, TimeUnit unit)用來獲取執(zhí)行結果,如果在指定時間內蛛淋,還沒獲取到結果咙好,就直接返回null。
也就是說Future提供了三種功能:
1)判斷任務是否完成褐荷;
2)能夠中斷任務勾效;
3)能夠獲取任務執(zhí)行結果。
因為Future只是一個接口叛甫,所以是無法直接用來創(chuàng)建對象使用的层宫,因此就有了下面的FutureTask。
FutureTask
先來看一下FutureTask的實現(xiàn):
public class FutureTask implementsRunnableFuture
FutureTask類實現(xiàn)了RunnableFuture接口其监,我們看一下RunnableFuture接口的實現(xiàn):
public interface RunnableFuture extends Runnable,Future {
??? void run();
}
可以看出RunnableFuture繼承了Runnable接口和Future接口萌腿,而FutureTask實現(xiàn)了RunnableFuture接口。所以它既可以作為Runnable被線程執(zhí)行抖苦,又可以作為Future得到Callable的返回值毁菱。FutureTask 可用于異步獲取執(zhí)行結果或取消執(zhí)行任務的場景米死。當一個計算任務需要執(zhí)行很長時間,那么就可以用 FutureTask 來封裝這個任務贮庞,主線程在完成自己的任務之后再去獲取結果峦筒。
事實上,F(xiàn)utureTask是Future接口的一個唯一實現(xiàn)類窗慎。
使用示例:
1.使用Callable+Future獲取執(zhí)行結果
public class Test {
??? public static voidmain(String[] args) {
??????? ExecutorServiceexecutor = Executors.newCachedThreadPool();
??????? Task task = newTask();
???????Future result = executor.submit(task);
??????? executor.shutdown();
??? }
??? try {
???????System.out.println("task運行結果"+result.get());
??? } catch(InterruptedException e) {
??????? e.printStackTrace();
??? } catch(ExecutionException e) {
??????? e.printStackTrace();
??? }
}
class Task implements Callable{
??? @Override
??? public Integer call()throws Exception {
???????System.out.println("子線程在進行計算");
??????? Thread.sleep(3000);
??????? int sum = 0;
??????? for(inti=0;i<100;i++)
??????????? sum += i;
??????? return sum;
??? }
}???
2.使用Callable+FutureTask獲取執(zhí)行結果
public class Test {
??? public static voidmain(String[] args) {
??????? //第一種方式
??????? ExecutorServiceexecutor = Executors.newCachedThreadPool();
??????? Task task = newTask();
???????FutureTask futureTask = newFutureTask(task);
???????executor.submit(futureTask);
??????? executor.shutdown();
??? }
? ? ?try {
??????System.out.println("task運行結果"+futureTask.get());
???? } catch(InterruptedException e) {
?????? e.printStackTrace();
???? } catch(ExecutionException e) {
?????? e.printStackTrace();
???? }
}
class Task implements Callable{
??? @Override
??? public Integer call()throws Exception {
???????System.out.println("子線程在進行計算");
??????? Thread.sleep(3000);
??????? int sum = 0;
??????? for(inti=0;i<100;i++)
??????????? sum += i;
??????? return sum;
??? }
}
3物喷、繼承 Thread 類
同樣也是需要實現(xiàn) run() 方法,因為 Thread類也實現(xiàn)了Runable 接口遮斥。
當調用 start() 方法啟動一個線程時峦失,虛擬機會將該線程放入就緒隊列中等待被調度,當一個線程被調度時會執(zhí)行該線程的 run() 方法术吗。
public class MyThread extends Thread {
??? public void run() {
??????? // ...
??? }
}
public static void main(String[] args) {
??? MyThread mt = new MyThread();
??? mt.start();
}
實現(xiàn)接口 VS 繼承Thread
實現(xiàn)接口會更好一些宠进,因為:
Java 不支持多重繼承,因此繼承了 Thread類就無法繼承其它類藐翎,但是可以實現(xiàn)多個接口;
類可能只要求可執(zhí)行就行实幕,繼承整個 Thread 類開銷過大吝镣。
Executor
Executor 管理多個異步任務的執(zhí)行,而無需程序員顯式地管理線程的生命周期昆庇。這里的異步是指多個任務的執(zhí)行互不干擾末贾,不需要進行同步操作。
主要有三種 Executor:
CachedThreadPool:一個任務創(chuàng)建一個線程整吆;
FixedThreadPool:所有任務只能使用固定大小的線程拱撵;
SingleThreadExecutor:相當于大小為 1 的FixedThreadPool。
Executors.newCachedThreadPool();??????? //創(chuàng)建一個線程池表蝙,容量大小為????????
????????????????????????????????????????????????????????????????????Integer.MAX_VALUE
Executors.newFixedThreadPool(int);??? //創(chuàng)建固定容量大小的線程池
Executors.newSingleThreadExecutor();?? //創(chuàng)建容量為1的線程池
public static void main(String[] args) {
??? ExecutorService executorService =Executors.newCachedThreadPool();
??? for (int i = 0; i < 5; i++) {
??????? executorService.execute(newMyRunnable());
??? }
??? executorService.shutdown();
}
守護線程(Daemon):
所謂守護線程是指在程序運行的時候在后臺提供一種通用服務的線程拴测,比如垃圾回收線程就是一個很稱職的守護者,并且這種線程并不屬于程序中不可或缺的部分府蛇。
在Java中有兩類線程:User Thread(用戶線程)集索、Daemon
Thread(守護線程)
用個比較通俗的比如,任何一個守護線程都是整個JVM中所有非守護線程的保姆汇跨;
只要當前JVM實例中尚存在任何一個非守護線程沒有結束务荆,守護線程就全部工作;只有當最后一個非守護線程結束時穷遂,守護線程隨著JVM一同結束工作函匕。
守護線程與普通線程的唯一區(qū)別是:當JVM中所有的線程都是守護線程的時候,JVM就可以退出了蚪黑;如果還有一個或以上的非守護線程則不會退出盅惜。(以上是針對正常退出中剩,調用System.exit則必定會退出)
守護線程并非只有虛擬機內部提供,用戶在編寫程序時也可以自己設置守護線程酷窥。用戶可以用Thread的setDaemon(true)方法設置當前線程為守護線程咽安。setDeamon(true)的唯一意義就是告訴JVM不需要等待它退出,讓JVM喜歡什么退出就退出吧蓬推,不用管它妆棒。
Thread daemonTread= new Thread();?
? //設定 daemonThread 為 守護線程,default false(非守護線程)?
?daemonThread.setDaemon(true);?
?//驗證當前線程是否為守護線程沸伏,返回 true 則為守護線程?
?daemonThread.isDaemon();?
這里有幾點需要注意:
(1) thread.setDaemon(true)必須在thread.start()之前設置糕珊,否則會跑出一個IllegalThreadStateException異常。你不能把正在運行的常規(guī)線程設置為守護線程毅糟。
(2) 在Daemon線程中產生的新線程也是Daemon的红选。
(3) 不要認為所有的應用都可以分配給Daemon來進行服務,比如讀寫操作或者計算邏輯姆另。
因為你不可能知道在所有的User完成之前喇肋,Daemon是否已經完成了預期的服務任務。一旦User退出了迹辐,可能大量數(shù)據(jù)還沒有來得及讀入或寫出蝶防,計算任務也可能多次運行結果不一樣。這對程序是毀滅性的明吩。造成這個結果理由已經說過了:一旦所有User Thread離開了间学,虛擬機也就退出運行了。
二印荔、線程狀態(tài)
????? Java中線程中狀態(tài)可分為五種:New(新建狀態(tài))低葫,Runnable(就緒狀態(tài)),Running(運行狀態(tài))仍律,Blocked(阻塞狀態(tài))嘿悬,Dead(死亡狀態(tài))。
New:新建狀態(tài)水泉,當線程創(chuàng)建完成時為新建狀態(tài)鹊漠,即new Thread(...),還沒有調用start方法時茶行,線程處于新建狀態(tài)躯概。
Runnable:就緒狀態(tài),當調用線程的的start方法后畔师,線程進入就緒狀態(tài)娶靡,等待CPU資源。處于就緒狀態(tài)的線程由Java運行時系統(tǒng)的線程調度程序(thread scheduler)來調度看锉。
Running:運行狀態(tài)姿锭,就緒狀態(tài)的線程獲取到CPU執(zhí)行權以后進入運行狀態(tài)塔鳍,開始執(zhí)行run方法。
? ? ? ?Blocked:阻塞表示線程在等待Monitor lock呻此。比如轮纫,線程試圖通過synchronized去獲取某個鎖,但是其他線程已經獨占了焚鲜,那么當前線程就會處于阻塞狀態(tài)掌唾。
? ? ? ?WAITING(等待):表示正在等待其他線程采取某些操作。一個常見的場景是類似生產者消費者模式忿磅,發(fā)現(xiàn)任務條件尚未滿足糯彬,就讓當前消費者線程等待(wait),另外的生產者線程去準備任務數(shù)據(jù)葱她,然后通過類似notify等動作撩扒,通知消費線程可以繼續(xù)工作了。Thread.join()也會令線程進入等待狀態(tài)吨些。
? ? ? ?TIMED_WAIT(計時等待):其進入條件和等待狀態(tài)類似搓谆,但是調用的是存在超時條件的方法,比如wait或join等方法的指定超時版本豪墅。
? ? ? ?TERMINATED(終止):不管是意外退出還是正常執(zhí)行結束挽拔,線程已經完成使命,終止運行但校,也有人把這個狀態(tài)叫作死亡。
以下是關系到線程運行狀態(tài)的幾個方法:
1)start方法
start()用來啟動一個線程啡氢,當調用start方法后状囱,系統(tǒng)才會開啟一個新的線程來執(zhí)行用戶定義的子任務,在這個過程中倘是,會為相應的線程分配需要的資源亭枷。
一個線程兩次調用start()方法會出現(xiàn)什么情況?
Java的線程是不允許啟動兩次的搀崭,第二次調用必然會拋出IllegalThreadStateException叨粘,這是一種運行時異常,多次調用start被認為是編程錯誤瘤睹。
2)run方法
run()方法是不需要用戶來調用的升敲,當通過start方法啟動一個線程之后,當線程獲得了CPU執(zhí)行時間轰传,便進入run方法體去執(zhí)行具體的任務驴党。注意,繼承Thread類必須重寫run方法获茬,在run方法中定義具體要執(zhí)行的任務港庄。
? ? ? ? 3)wait/notify/notifyAll方法的使用
??? ????wait()使當前線程阻塞倔既,前提是必須先獲得鎖,一般配合synchronized關鍵字使用鹏氧,即一般在synchronized同步代碼塊里使用wait()渤涌、notify/notifyAll()方法。
當線程執(zhí)行wait()方法時候把还,會釋放當前的鎖实蓬,然后讓出CPU,進入等待狀態(tài)笨篷。
???? 既然wait方式是通過對象的monitor對象來實現(xiàn)的瞳秽,所以只要在同一對象上去調用
???? notify/notifyAll方法,就可以喚醒對應對象monitor上等待的線程了率翅。
? ? 只有當notify/notifyAll()被執(zhí)行時候练俐,才會喚醒一個或多個正處于等待狀態(tài)的線程,然后繼續(xù)往下執(zhí)行冕臭,直到執(zhí)行完synchronized代碼塊的代碼或是中途遇到wait()腺晾,再次釋放鎖。
也就是說辜贵,notify/notifyAll()的執(zhí)行只是喚醒沉睡的線程悯蝉,而不會立即釋放鎖,鎖的釋放要看代碼塊的具體執(zhí)行情況托慨。所以在編程中鼻由,盡量在使用了notify/notifyAll()后立即退出臨界區(qū),以喚醒其他線程厚棵。
? ? notify和wait的順序不能錯蕉世,如果A線程先執(zhí)行notify方法,B線程再執(zhí)行wait方法婆硬,那么B線程是無法被喚醒的狠轻。
? ? ?notify和notifyAll的區(qū)別
??? notify方法只喚醒一個等待(對象的)線程并使該線程開始執(zhí)行。所以如果有多個線程等待一個對象彬犯,這個方法只會喚醒其中一個線程向楼,選擇哪個線程取決于操作系統(tǒng)對多線程管理的實現(xiàn)。
? ? notifyAll會喚醒所有等待(對象的)線程谐区,盡管哪一個線程將會第一個處理取決于操作系統(tǒng)的實現(xiàn)湖蜕。如果當前情況下有多個線程需要被喚醒,推薦使用notifyAll方法宋列。
? ? 最后重荠,有兩點點需要注意:
??? (1)調用wait方法后,線程是會釋放對monitor對象的所有權的。
??? (2)一個通過wait方法阻塞的線程戈鲁,必須同時滿足以下兩個條件才能被真正執(zhí)行:
? ? ? ? ? ? ? ? 線程需要被喚醒(超時喚醒或調用notify/notifyll)仇参。
? ? ? ? ? ? ? ? 線程喚醒后需要競爭到鎖(monitor)。
? ? 4)sleep/yield/join方法解析
??? ??????這組方法跟上面方法的最明顯區(qū)別是:這幾個方法都位于Thread類中婆殿,而上面三個方法都位于Object類中诈乒。
??? (1)sleep方法
???????? sleep方法的作用是讓當前線程暫停指定的時間(毫秒),sleep方法是最簡單的方法婆芦,在上述的例子中也用到過怕磨,比較容易理解。唯一需要注意的是其與wait方法的區(qū)別消约。
???????? 最簡單的區(qū)別是肠鲫,wait方法依賴于同步,而sleep方法可以直接調用或粮。而更深層次的區(qū)別在于sleep方法只是暫時讓出CPU的執(zhí)行權导饲,并不釋放鎖。而wait方法則需要釋放鎖氯材。
??? (2)yield方法
???????? 調用yield方法會讓當前線程交出CPU權限渣锦,讓CPU去執(zhí)行其他的線程。它跟sleep方法類似氢哮,同樣不會釋放鎖袋毙。但是yield不能控制具體的交出CPU的時間,另外冗尤,
???????? yield方法只能讓擁有相同優(yōu)先級的線程有獲取CPU執(zhí)行時間的機會听盖。yield方法只是將Running狀態(tài)轉變?yōu)镽unnable狀態(tài)。
???? 注意裂七,調用yield方法并不會讓線程進入阻塞狀態(tài)皆看,而是讓線程重回就緒狀態(tài),它只需要等待重新獲取CPU執(zhí)行時間碍讯,這一點是和sleep方法不一樣的。
??? (3)join方法
???????? join方法有三個重載版本:
???????? join()
???????? join(long millis)???? //參數(shù)為毫秒
???????? join(long millis,int nanoseconds)??? //第一參數(shù)為毫秒扯躺,第二個參數(shù)為納秒????????
???????? join方法的作用是父線程等待子線程執(zhí)行完成后再執(zhí)行捉兴,換句話說就是將異步執(zhí)行的線程合并為同步的線程。如果調用的是無參join方法录语,則等待thread執(zhí)行完畢倍啥,如果調用的是指定了時間參數(shù)的join方法,則等待一定的時間澎埠∷渎疲可以看出join方法就是通過wait方法來將線程的阻塞,如果join的線程還在執(zhí)行蒲稳,則將當前線程阻塞起來氮趋,直到join的線程執(zhí)行完成伍派,當前線程才能執(zhí)行。由于wait方法會讓線程釋放對象鎖剩胁,所以join方法同樣會讓線程釋放對一個對象持有的鎖诉植。
???????? 不過有一點需要注意,這里的join只調用了wait方法昵观,卻沒有對應的notify方法晾腔,原因是Thread的start方法中做了相應的處理,所以當join的線程執(zhí)行完成以后啊犬,會自動喚醒主線程繼續(xù)往下執(zhí)行灼擂。