一、使用線程
有三種使用線程的方法:
* 實(shí)現(xiàn) Runnable 接口;
* 實(shí)現(xiàn) Callable 接口;
* 繼承 Thread 類。
實(shí)現(xiàn) Runnable 和Callable 接口的類只能當(dāng)做一個(gè)可以在線程中運(yùn)行的任務(wù),不是真正意義上的線程,因此最后還需要通過 Thread來調(diào)用??梢哉f任務(wù)是通過線程驅(qū)動(dòng)從而執(zhí)行的。
1、實(shí)現(xiàn) Runnable 接口
需要實(shí)現(xiàn) run() 方法。
通過 Thread 調(diào)用start() 方法來啟動(dòng)線程。
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、實(shí)現(xiàn) Callable 接口
與 Runnable 相比,Callable可以有返回值,返回值通過FutureTask 進(jìn)行封裝。
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吧,它是一個(gè)接口,在它里面只聲明了一個(gè)run()方法:
public interface Runnable {
????? public abstract void run();
}
由于run()方法返回值為void類型,所以在執(zhí)行完任務(wù)之后無法返回任何結(jié)果。
Callable位于java.util.concurrent包下,它也是一個(gè)接口,在它里面也只聲明了一個(gè)方法,只不過這個(gè)方法叫做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;
}
可以看到,這是一個(gè)泛型接口,call()函數(shù)返回的類型就是傳遞進(jìn)來的V類型。
那么怎么使用Callable呢?一般情況下是配合ExecutorService來使用的,在ExecutorService接口中聲明了若干個(gè)submit方法的重載版本:
Future submit(Callable task);
Future submit(Runnable task, T result);
Future submit(Runnable task);
Future
Future就是對于具體的Runnable或者Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消、查詢是否完成、獲取結(jié)果。必要時(shí)可以通過get方法獲取執(zhí)行結(jié)果,該方法會(huì)阻塞直到任務(wù)返回結(jié)果。
Future類位于java.util.concurrent包下,它是一個(gè)接口,在Future接口中聲明了5個(gè)方法,下面依次解釋每個(gè)方法的作用:
cancel方法用來取消任務(wù),如果取消任務(wù)成功則返回true,如果取消任務(wù)失敗則返回false。
isCancelled方法表示任務(wù)是否被取消成功,如果在任務(wù)正常完成前被取消成功,則返回 true。
isDone方法表示任務(wù)是否已經(jīng)完成,若任務(wù)完成,則返回true;
get()方法用來獲取執(zhí)行結(jié)果,這個(gè)方法會(huì)產(chǎn)生阻塞,會(huì)一直等到任務(wù)執(zhí)行完畢才返回;get(long timeout, TimeUnit unit)用來獲取執(zhí)行結(jié)果,如果在指定時(shí)間內(nèi),還沒獲取到結(jié)果,就直接返回null。
也就是說Future提供了三種功能:
1)判斷任務(wù)是否完成;
2)能夠中斷任務(wù);
3)能夠獲取任務(wù)執(zhí)行結(jié)果。
因?yàn)镕uture只是一個(gè)接口,所以是無法直接用來創(chuàng)建對象使用的,因此就有了下面的FutureTask。
FutureTask
先來看一下FutureTask的實(shí)現(xiàn):
public class FutureTask implementsRunnableFuture
FutureTask類實(shí)現(xiàn)了RunnableFuture接口,我們看一下RunnableFuture接口的實(shí)現(xiàn):
public interface RunnableFuture extends Runnable,Future {
??? void run();
}
可以看出RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實(shí)現(xiàn)了RunnableFuture接口。所以它既可以作為Runnable被線程執(zhí)行,又可以作為Future得到Callable的返回值。FutureTask 可用于異步獲取執(zhí)行結(jié)果或取消執(zhí)行任務(wù)的場景。當(dāng)一個(gè)計(jì)算任務(wù)需要執(zhí)行很長時(shí)間,那么就可以用 FutureTask 來封裝這個(gè)任務(wù),主線程在完成自己的任務(wù)之后再去獲取結(jié)果。
事實(shí)上,F(xiàn)utureTask是Future接口的一個(gè)唯一實(shí)現(xiàn)類。
使用示例:
1.使用Callable+Future獲取執(zhí)行結(jié)果
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運(yùn)行結(jié)果"+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("子線程在進(jìn)行計(jì)算");
??????? Thread.sleep(3000);
??????? int sum = 0;
??????? for(inti=0;i<100;i++)
??????????? sum += i;
??????? return sum;
??? }
}???
2.使用Callable+FutureTask獲取執(zhí)行結(jié)果
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運(yùn)行結(jié)果"+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("子線程在進(jìn)行計(jì)算");
??????? Thread.sleep(3000);
??????? int sum = 0;
??????? for(inti=0;i<100;i++)
??????????? sum += i;
??????? return sum;
??? }
}
3、繼承 Thread 類
同樣也是需要實(shí)現(xiàn) run() 方法,因?yàn)?Thread類也實(shí)現(xiàn)了Runable 接口。
當(dāng)調(diào)用 start() 方法啟動(dòng)一個(gè)線程時(shí),虛擬機(jī)會(huì)將該線程放入就緒隊(duì)列中等待被調(diào)度,當(dāng)一個(gè)線程被調(diào)度時(shí)會(huì)執(zhí)行該線程的 run() 方法。
public class MyThread extends Thread {
??? public void run() {
??????? // ...
??? }
}
public static void main(String[] args) {
??? MyThread mt = new MyThread();
??? mt.start();
}
實(shí)現(xiàn)接口 VS 繼承Thread
實(shí)現(xiàn)接口會(huì)更好一些,因?yàn)椋?/p>
Java 不支持多重繼承,因此繼承了 Thread類就無法繼承其它類,但是可以實(shí)現(xiàn)多個(gè)接口;
類可能只要求可執(zhí)行就行,繼承整個(gè) Thread 類開銷過大。
Executor
Executor 管理多個(gè)異步任務(wù)的執(zhí)行,而無需程序員顯式地管理線程的生命周期。這里的異步是指多個(gè)任務(wù)的執(zhí)行互不干擾,不需要進(jìn)行同步操作。
主要有三種 Executor:
CachedThreadPool:一個(gè)任務(wù)創(chuàng)建一個(gè)線程;
FixedThreadPool:所有任務(wù)只能使用固定大小的線程;
SingleThreadExecutor:相當(dāng)于大小為 1 的FixedThreadPool。
Executors.newCachedThreadPool();??????? //創(chuàng)建一個(gè)線程池,容量大小為????????
????????????????????????????????????????????????????????????????????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();
}
守護(hù)線程(Daemon):
所謂守護(hù)線程是指在程序運(yùn)行的時(shí)候在后臺(tái)提供一種通用服務(wù)的線程,比如垃圾回收線程就是一個(gè)很稱職的守護(hù)者,并且這種線程并不屬于程序中不可或缺的部分。
在Java中有兩類線程:User Thread(用戶線程)、Daemon
Thread(守護(hù)線程)
用個(gè)比較通俗的比如,任何一個(gè)守護(hù)線程都是整個(gè)JVM中所有非守護(hù)線程的保姆;
只要當(dāng)前JVM實(shí)例中尚存在任何一個(gè)非守護(hù)線程沒有結(jié)束,守護(hù)線程就全部工作;只有當(dāng)最后一個(gè)非守護(hù)線程結(jié)束時(shí),守護(hù)線程隨著JVM一同結(jié)束工作。
守護(hù)線程與普通線程的唯一區(qū)別是:當(dāng)JVM中所有的線程都是守護(hù)線程的時(shí)候,JVM就可以退出了;如果還有一個(gè)或以上的非守護(hù)線程則不會(huì)退出。(以上是針對正常退出,調(diào)用System.exit則必定會(huì)退出)
守護(hù)線程并非只有虛擬機(jī)內(nèi)部提供,用戶在編寫程序時(shí)也可以自己設(shè)置守護(hù)線程。用戶可以用Thread的setDaemon(true)方法設(shè)置當(dāng)前線程為守護(hù)線程。setDeamon(true)的唯一意義就是告訴JVM不需要等待它退出,讓JVM喜歡什么退出就退出吧,不用管它。
Thread daemonTread= new Thread();?
? //設(shè)定 daemonThread 為 守護(hù)線程,default false(非守護(hù)線程)?
?daemonThread.setDaemon(true);?
?//驗(yàn)證當(dāng)前線程是否為守護(hù)線程,返回 true 則為守護(hù)線程?
?daemonThread.isDaemon();?
這里有幾點(diǎn)需要注意:
(1) thread.setDaemon(true)必須在thread.start()之前設(shè)置,否則會(huì)跑出一個(gè)IllegalThreadStateException異常。你不能把正在運(yùn)行的常規(guī)線程設(shè)置為守護(hù)線程。
(2) 在Daemon線程中產(chǎn)生的新線程也是Daemon的。
(3) 不要認(rèn)為所有的應(yīng)用都可以分配給Daemon來進(jìn)行服務(wù),比如讀寫操作或者計(jì)算邏輯。
因?yàn)槟悴豢赡苤涝谒械腢ser完成之前,Daemon是否已經(jīng)完成了預(yù)期的服務(wù)任務(wù)。一旦User退出了,可能大量數(shù)據(jù)還沒有來得及讀入或?qū)懗?,?jì)算任務(wù)也可能多次運(yùn)行結(jié)果不一樣。這對程序是毀滅性的。造成這個(gè)結(jié)果理由已經(jīng)說過了:一旦所有User Thread離開了,虛擬機(jī)也就退出運(yùn)行了。
二、線程狀態(tài)
????? Java中線程中狀態(tài)可分為五種:New(新建狀態(tài)),Runnable(就緒狀態(tài)),Running(運(yùn)行狀態(tài)),Blocked(阻塞狀態(tài)),Dead(死亡狀態(tài))。
New:新建狀態(tài),當(dāng)線程創(chuàng)建完成時(shí)為新建狀態(tài),即new Thread(...),還沒有調(diào)用start方法時(shí),線程處于新建狀態(tài)。
Runnable:就緒狀態(tài),當(dāng)調(diào)用線程的的start方法后,線程進(jìn)入就緒狀態(tài),等待CPU資源。處于就緒狀態(tài)的線程由Java運(yùn)行時(shí)系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度。
Running:運(yùn)行狀態(tài),就緒狀態(tài)的線程獲取到CPU執(zhí)行權(quán)以后進(jìn)入運(yùn)行狀態(tài),開始執(zhí)行run方法。
? ? ? ?Blocked:阻塞表示線程在等待Monitor lock。比如,線程試圖通過synchronized去獲取某個(gè)鎖,但是其他線程已經(jīng)獨(dú)占了,那么當(dāng)前線程就會(huì)處于阻塞狀態(tài)。
? ? ? ?WAITING(等待):表示正在等待其他線程采取某些操作。一個(gè)常見的場景是類似生產(chǎn)者消費(fèi)者模式,發(fā)現(xiàn)任務(wù)條件尚未滿足,就讓當(dāng)前消費(fèi)者線程等待(wait),另外的生產(chǎn)者線程去準(zhǔn)備任務(wù)數(shù)據(jù),然后通過類似notify等動(dòng)作,通知消費(fèi)線程可以繼續(xù)工作了。Thread.join()也會(huì)令線程進(jìn)入等待狀態(tài)。
? ? ? ?TIMED_WAIT(計(jì)時(shí)等待):其進(jìn)入條件和等待狀態(tài)類似,但是調(diào)用的是存在超時(shí)條件的方法,比如wait或join等方法的指定超時(shí)版本。
? ? ? ?TERMINATED(終止):不管是意外退出還是正常執(zhí)行結(jié)束,線程已經(jīng)完成使命,終止運(yùn)行,也有人把這個(gè)狀態(tài)叫作死亡。


以下是關(guān)系到線程運(yùn)行狀態(tài)的幾個(gè)方法:
1)start方法
start()用來啟動(dòng)一個(gè)線程,當(dāng)調(diào)用start方法后,系統(tǒng)才會(huì)開啟一個(gè)新的線程來執(zhí)行用戶定義的子任務(wù),在這個(gè)過程中,會(huì)為相應(yīng)的線程分配需要的資源。
一個(gè)線程兩次調(diào)用start()方法會(huì)出現(xiàn)什么情況?
Java的線程是不允許啟動(dòng)兩次的,第二次調(diào)用必然會(huì)拋出IllegalThreadStateException,這是一種運(yùn)行時(shí)異常,多次調(diào)用start被認(rèn)為是編程錯(cuò)誤。
2)run方法
run()方法是不需要用戶來調(diào)用的,當(dāng)通過start方法啟動(dòng)一個(gè)線程之后,當(dāng)線程獲得了CPU執(zhí)行時(shí)間,便進(jìn)入run方法體去執(zhí)行具體的任務(wù)。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執(zhí)行的任務(wù)。
? ? ? ? 3)wait/notify/notifyAll方法的使用
??? ????wait()使當(dāng)前線程阻塞,前提是必須先獲得鎖,一般配合synchronized關(guān)鍵字使用,即一般在synchronized同步代碼塊里使用wait()、notify/notifyAll()方法。
當(dāng)線程執(zhí)行wait()方法時(shí)候,會(huì)釋放當(dāng)前的鎖,然后讓出CPU,進(jìn)入等待狀態(tài)。
???? 既然wait方式是通過對象的monitor對象來實(shí)現(xiàn)的,所以只要在同一對象上去調(diào)用
???? notify/notifyAll方法,就可以喚醒對應(yīng)對象monitor上等待的線程了。
? ? 只有當(dāng)notify/notifyAll()被執(zhí)行時(shí)候,才會(huì)喚醒一個(gè)或多個(gè)正處于等待狀態(tài)的線程,然后繼續(xù)往下執(zhí)行,直到執(zhí)行完synchronized代碼塊的代碼或是中途遇到wait(),再次釋放鎖。
也就是說,notify/notifyAll()的執(zhí)行只是喚醒沉睡的線程,而不會(huì)立即釋放鎖,鎖的釋放要看代碼塊的具體執(zhí)行情況。所以在編程中,盡量在使用了notify/notifyAll()后立即退出臨界區(qū),以喚醒其他線程。
? ? notify和wait的順序不能錯(cuò),如果A線程先執(zhí)行notify方法,B線程再執(zhí)行wait方法,那么B線程是無法被喚醒的。
? ? ?notify和notifyAll的區(qū)別
??? notify方法只喚醒一個(gè)等待(對象的)線程并使該線程開始執(zhí)行。所以如果有多個(gè)線程等待一個(gè)對象,這個(gè)方法只會(huì)喚醒其中一個(gè)線程,選擇哪個(gè)線程取決于操作系統(tǒng)對多線程管理的實(shí)現(xiàn)。
? ? notifyAll會(huì)喚醒所有等待(對象的)線程,盡管哪一個(gè)線程將會(huì)第一個(gè)處理取決于操作系統(tǒng)的實(shí)現(xiàn)。如果當(dāng)前情況下有多個(gè)線程需要被喚醒,推薦使用notifyAll方法。
? ? 最后,有兩點(diǎn)點(diǎn)需要注意:
??? (1)調(diào)用wait方法后,線程是會(huì)釋放對monitor對象的所有權(quán)的。
??? (2)一個(gè)通過wait方法阻塞的線程,必須同時(shí)滿足以下兩個(gè)條件才能被真正執(zhí)行:
? ? ? ? ? ? ? ? 線程需要被喚醒(超時(shí)喚醒或調(diào)用notify/notifyll)。
? ? ? ? ? ? ? ? 線程喚醒后需要競爭到鎖(monitor)。
? ? 4)sleep/yield/join方法解析
??? ??????這組方法跟上面方法的最明顯區(qū)別是:這幾個(gè)方法都位于Thread類中,而上面三個(gè)方法都位于Object類中。
??? (1)sleep方法
???????? sleep方法的作用是讓當(dāng)前線程暫停指定的時(shí)間(毫秒),sleep方法是最簡單的方法,在上述的例子中也用到過,比較容易理解。唯一需要注意的是其與wait方法的區(qū)別。
???????? 最簡單的區(qū)別是,wait方法依賴于同步,而sleep方法可以直接調(diào)用。而更深層次的區(qū)別在于sleep方法只是暫時(shí)讓出CPU的執(zhí)行權(quán),并不釋放鎖。而wait方法則需要釋放鎖。
??? (2)yield方法
???????? 調(diào)用yield方法會(huì)讓當(dāng)前線程交出CPU權(quán)限,讓CPU去執(zhí)行其他的線程。它跟sleep方法類似,同樣不會(huì)釋放鎖。但是yield不能控制具體的交出CPU的時(shí)間,另外,
???????? yield方法只能讓擁有相同優(yōu)先級(jí)的線程有獲取CPU執(zhí)行時(shí)間的機(jī)會(huì)。yield方法只是將Running狀態(tài)轉(zhuǎn)變?yōu)镽unnable狀態(tài)。
???? 注意,調(diào)用yield方法并不會(huì)讓線程進(jìn)入阻塞狀態(tài),而是讓線程重回就緒狀態(tài),它只需要等待重新獲取CPU執(zhí)行時(shí)間,這一點(diǎn)是和sleep方法不一樣的。
??? (3)join方法
???????? join方法有三個(gè)重載版本:
???????? join()
???????? join(long millis)???? //參數(shù)為毫秒
???????? join(long millis,int nanoseconds)??? //第一參數(shù)為毫秒,第二個(gè)參數(shù)為納秒????????
???????? join方法的作用是父線程等待子線程執(zhí)行完成后再執(zhí)行,換句話說就是將異步執(zhí)行的線程合并為同步的線程。如果調(diào)用的是無參join方法,則等待thread執(zhí)行完畢,如果調(diào)用的是指定了時(shí)間參數(shù)的join方法,則等待一定的時(shí)間??梢钥闯鰆oin方法就是通過wait方法來將線程的阻塞,如果join的線程還在執(zhí)行,則將當(dāng)前線程阻塞起來,直到j(luò)oin的線程執(zhí)行完成,當(dāng)前線程才能執(zhí)行。由于wait方法會(huì)讓線程釋放對象鎖,所以join方法同樣會(huì)讓線程釋放對一個(gè)對象持有的鎖。
???????? 不過有一點(diǎn)需要注意,這里的join只調(diào)用了wait方法,卻沒有對應(yīng)的notify方法,原因是Thread的start方法中做了相應(yīng)的處理,所以當(dāng)join的線程執(zhí)行完成以后,會(huì)自動(dòng)喚醒主線程繼續(xù)往下執(zhí)行。