??jdk1.5之后加入了java.util.concurrent包,這個(gè)包主要介紹java中線程和線程池的使用敷燎。
Java多線程實(shí)現(xiàn)的四種方式
Java多線程實(shí)現(xiàn)方式主要有四種:繼承Thread類玻墅、實(shí)現(xiàn)Runnable接口够庙、實(shí)現(xiàn)Callable接口通過(guò)FutureTask包裝器來(lái)創(chuàng)建Thread線程伍伤、使用ExecutorService乳蛾、Callable抛杨、Future實(shí)現(xiàn)有返回結(jié)果的多線程够委。其中前兩種方式線程執(zhí)行完后都沒(méi)有返回值,后兩種是帶返回值的蝶桶。
1. 繼承Thread類創(chuàng)建線程
Thread類本質(zhì)上是實(shí)現(xiàn)了Runnable接口的一個(gè)實(shí)例慨绳,代表一個(gè)線程的實(shí)例。啟動(dòng)線程的唯一方法就是通過(guò)Thread類的start()實(shí)例方法真竖。start()方法是一個(gè)native方法脐雪,它將啟動(dòng)一個(gè)新線程,并執(zhí)行run()方法恢共。這種方式實(shí)現(xiàn)多線程很簡(jiǎn)單战秋,通過(guò)自己的類直接extend Thread,并復(fù)寫(xiě)run()方法讨韭,就可以啟動(dòng)新線程并執(zhí)行自己定義的run()方法脂信。例如:
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
2. 實(shí)現(xiàn)Runnable接口創(chuàng)建線程
如果自己的類已經(jīng)extends另一個(gè)類癣蟋,就無(wú)法直接extends Thread,此時(shí)狰闪,可以實(shí)現(xiàn)一個(gè)Runnable接口疯搅,如下:
為了啟動(dòng)MyThread,需要首先實(shí)例化一個(gè)Thread埋泵,并傳入自己的MyThread實(shí)例:
事實(shí)上幔欧,當(dāng)傳入一個(gè)Runnable target參數(shù)給Thread后,Thread的run()方法就會(huì)調(diào)用target.run()丽声,參考JDK源代碼:
3. 實(shí)現(xiàn)Callable接口通過(guò)FutureTask包裝器來(lái)創(chuàng)建Thread線程
Callable接口(也只有一個(gè)方法)定義如下:
public interface Callable<V> { V call() throws Exception;}
public class SomeCallable<V> extends OtherClass implements Callable<V> {
@Override
public V call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
Callable<V> oneCallable = new SomeCallable<V>();
//由Callable<Integer>創(chuàng)建一個(gè)FutureTask<Integer>對(duì)象
FutureTask<V> oneTask = new FutureTask<V>(oneCallable);
//注釋:FutureTask<Integer>是一個(gè)包裝器礁蔗,它通過(guò)接受Callable<Integer>來(lái)創(chuàng)建,它同時(shí)實(shí)現(xiàn)了Future和Runnable接口雁社。
//由FutureTask<Integer>創(chuàng)建一個(gè)Thread對(duì)象:
Thread oneThread = new Thread(oneTask);
oneThread.start();
//至此浴井,一個(gè)線程就創(chuàng)建完成了。
4. 使用ExecutorService霉撵、Callable磺浙、Future實(shí)現(xiàn)有返回結(jié)果的線
ExecutorService、Callable徒坡、Future三個(gè)接口實(shí)際上都是屬于Executor框架屠缭。返回結(jié)果的線程是在JDK1.5中引入的新特征,有了這種特征就不需要再為了得到返回值而大費(fèi)周折了崭参。而且自己實(shí)現(xiàn)了也可能漏洞百出。
可返回值的任務(wù)必須實(shí)現(xiàn)Callable接口款咖。類似的何暮,無(wú)返回值的任務(wù)必須實(shí)現(xiàn)Runnable接口。
執(zhí)行Callable任務(wù)后铐殃,可以獲取一個(gè)Future的對(duì)象海洼,在該對(duì)象上調(diào)用get就可以獲取到Callable任務(wù)返回的Object了。
注意:get方法是阻塞的富腊,即:線程無(wú)返回結(jié)果坏逢,get方法會(huì)一直等待。
再結(jié)合線程池接口ExecutorService就可以實(shí)現(xiàn)傳說(shuō)中有返回結(jié)果的多線程了赘被。
下面提供了一個(gè)完整的有返回結(jié)果的多線程測(cè)試?yán)邮钦贘DK1.5下驗(yàn)證過(guò)沒(méi)問(wèn)題可以直接使用。代碼如下:
import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;
/**
* 有返回值的線程
*/
@SuppressWarnings("unchecked")
public class Test {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println("----程序開(kāi)始運(yùn)行----");
Date date1 = new Date();
int taskSize = 5;
// 創(chuàng)建一個(gè)線程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 創(chuàng)建多個(gè)有返回值的任務(wù)
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 執(zhí)行任務(wù)并獲取Future對(duì)象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString());
list.add(f);
}
// 關(guān)閉線程池
pool.shutdown();
// 獲取所有并發(fā)任務(wù)的運(yùn)行結(jié)果
for (Future f : list) {
// 從Future對(duì)象上獲取任務(wù)的返回值民假,并輸出到控制臺(tái)
System.out.println(">>>" + f.get().toString());
}
Date date2 = new Date();
System.out.println("----程序結(jié)束運(yùn)行----浮入,程序運(yùn)行時(shí)間【"
+ (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
class MyCallable implements Callable<Object> {
private String taskNum;
MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任務(wù)啟動(dòng)");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任務(wù)終止");
return taskNum + "任務(wù)返回運(yùn)行結(jié)果,當(dāng)前任務(wù)時(shí)間【" + time + "毫秒】";
}
}
代碼說(shuō)明:
上述代碼中Executors類,提供了一系列工廠方法用于創(chuàng)建線程池羊异,返回的線程池都實(shí)現(xiàn)了ExecutorService接口事秀。
public static ExecutorService newFixedThreadPool(int nThreads)
創(chuàng)建固定數(shù)目線程的線程池彤断。
public static ExecutorService newCachedThreadPool()
創(chuàng)建一個(gè)可緩存的線程池,調(diào)用execute 將重用以前構(gòu)造的線程(如果線程可用)易迹。如果現(xiàn)有線程沒(méi)有可用的宰衙,則創(chuàng)建一個(gè)新線程并添加到池中。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程睹欲。
public static ExecutorService newSingleThreadExecutor()
創(chuàng)建一個(gè)單線程化的Executor供炼。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
創(chuàng)建一個(gè)支持定時(shí)及周期性的任務(wù)執(zhí)行的線程池,多數(shù)情況下可用來(lái)替代Timer類句伶。
ExecutoreService提供了submit()方法劲蜻,傳遞一個(gè)Callable,或Runnable考余,返回Future先嬉。如果Executor后臺(tái)線程池還沒(méi)有完成Callable的計(jì)算,這調(diào)用返回Future對(duì)象的get()方法楚堤,會(huì)阻塞直到計(jì)算完成疫蔓。
- 【強(qiáng)制】線程資源必須通過(guò)線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程。
說(shuō)明:使用線程池的好處是減少在創(chuàng)建和銷毀線程上所花的時(shí)間以及系統(tǒng)資源的開(kāi)銷,解決資
源不足的問(wèn)題身冬。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者
“過(guò)度切換”的問(wèn)題衅胀。- 【強(qiáng)制】線程池不允許使用 Executors 去創(chuàng)建,而是通過(guò) ThreadPoolExecutor 的方式,這樣的處理方式讓寫(xiě)的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
--引自《阿里巴巴JAVA開(kāi)發(fā)手冊(cè)(紀(jì)念版)》編程規(guī)約第六條并發(fā)處理酥筝,第3和第4點(diǎn)
線程池的作用:
線程池作用就是限制系統(tǒng)中執(zhí)行線程的數(shù)量滚躯。根據(jù)系統(tǒng)的環(huán)境情況,可以自動(dòng)或手動(dòng)設(shè)置線程數(shù)量嘿歌,達(dá)到運(yùn)行的最佳效果掸掏;少了浪費(fèi)了系統(tǒng)資源,多了造成系統(tǒng)擁擠效率不高宙帝。用線程池控制線程數(shù)量丧凤,其他線程排隊(duì)等候。一個(gè)任務(wù)執(zhí)行完畢步脓,再?gòu)年?duì)列的中取最前面的任務(wù)開(kāi)始執(zhí)行愿待。若隊(duì)列中沒(méi)有等待進(jìn)程,線程池的這一資源處于等待靴患。當(dāng)一個(gè)新任務(wù)需要運(yùn)行時(shí)仍侥,如果線程池中有等待的工作線程,就可以開(kāi)始運(yùn)行了鸳君;否則進(jìn)入等待隊(duì)列访圃。
使用線程池的原因:
- 減少了創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程都可以被重復(fù)利用相嵌,可執(zhí)行多個(gè)任務(wù)腿时。
- 可以根據(jù)系統(tǒng)的承受能力况脆,調(diào)整線程池中工作線線程的數(shù)目,防止因?yàn)橄倪^(guò)多的內(nèi)存批糟,而把服務(wù)器累趴下(每個(gè)線程需要大約1MB內(nèi)存格了,線程開(kāi)的越多,消耗的內(nèi)存也就越大徽鼎,最后死機(jī))盛末。
Java里面線程池的頂級(jí)接口是Executor,但是嚴(yán)格意義上講Executor并不是一個(gè)線程池否淤,而只是一個(gè)執(zhí)行線程的工具悄但。真正的線程池接口是ExecutorService。
比較重要的幾個(gè)類:
接口 | 作用 |
---|---|
ExecutorService | 真正的線程池接口 |
ScheduledExecutorService | 能和Timer/TimerTask類似石抡,解決那些需要任務(wù)重復(fù)執(zhí)行的問(wèn)題檐嚣。 |
ThreadPoolExecutor | ExecutorService的默認(rèn)實(shí)現(xiàn)。 |
ScheduledThreadPoolExecutor | 繼承ThreadPoolExecutor的ScheduledExecutorService接口實(shí)現(xiàn)啰扛,周期性任務(wù)調(diào)度的類實(shí)現(xiàn)嚎京。 |
???要配置一個(gè)線程池是比較復(fù)雜的,尤其是對(duì)于線程池的原理不是很清楚的情況下隐解,很有可能配置的線程池不是較優(yōu)的鞍帝,因此在Executors類里面提供了一些靜態(tài)工廠,生成一些常用的線程池煞茫。
- newSingleThreadExecutor
創(chuàng)建一個(gè)單線程的線程池帕涌。這個(gè)線程池只有一個(gè)線程在工作,也就是相當(dāng)于單線程串行執(zhí)行所有任務(wù)续徽。如果這個(gè)唯一的線程因?yàn)楫惓=Y(jié)束宵膨,那么會(huì)有一個(gè)新的線程來(lái)替代它。此線程池保證所有任務(wù)的執(zhí)行順序按照任務(wù)的提交順序執(zhí)行炸宵。
- newFixedThreadPool
創(chuàng)建固定大小的線程池。每次提交一個(gè)任務(wù)就創(chuàng)建一個(gè)線程谷扣,直到線程達(dá)到線程池的最大大小土全。線程池的大小一旦達(dá)到最大值就會(huì)保持不變,如果某個(gè)線程因?yàn)閳?zhí)行異常而結(jié)束会涎,那么線程池會(huì)補(bǔ)充一個(gè)新線程裹匙。
- newCachedThreadPool
創(chuàng)建一個(gè)可緩存的線程池。如果線程池的大小超過(guò)了處理任務(wù)所需要的線程末秃,
那么就會(huì)回收部分空閑(60秒不執(zhí)行任務(wù))的線程概页,當(dāng)任務(wù)數(shù)增加時(shí),此線程池又可以智能的添加新線程來(lái)處理任務(wù)练慕。此線程池不會(huì)對(duì)線程池大小做限制惰匙,線程池大小完全依賴于操作系統(tǒng)(或者說(shuō)JVM)能夠創(chuàng)建的最大線程大小技掏。
- newScheduledThreadPool
創(chuàng)建一個(gè)大小無(wú)限的線程池。此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求项鬼。
ThreadPoolExecutor詳解
ThreadPoolExecutor的完整構(gòu)造方法的簽名是:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize //池中所保存的線程數(shù)哑梳,包括空閑線程。
maximumPoolSize //池中允許的最大線程數(shù)绘盟。
keepAliveTime //當(dāng)線程數(shù)大于核心時(shí)鸠真,此為終止前多余的空閑線程等待新任務(wù)的最長(zhǎng)時(shí)間。
unit //keepAliveTime參數(shù)的時(shí)間單位龄毡。
workQueue //執(zhí)行前用于保持任務(wù)的隊(duì)列吠卷。此隊(duì)列僅保持由 execute方法提交的 Runnable任務(wù)。
threadFactory //執(zhí)行程序創(chuàng)建新線程時(shí)使用的工廠沦零。
handler //由于超出線程范圍和隊(duì)列容量而使執(zhí)行被阻塞時(shí)所使用的處理程序祭隔。
ThreadPoolExecutor是Executors類的底層實(shí)現(xiàn)。
???在JDK幫助文檔中蠢终,有如此一段話:“強(qiáng)烈建議程序員使用較為方便的Executors工廠方法Executors.newCachedThreadPool()(無(wú)界線程池序攘,可以進(jìn)行自動(dòng)線程回收)、Executors.newFixedThreadPool(int)(固定大小線程池)Executors.newSingleThreadExecutor()(單個(gè)后臺(tái)線程)
它們均為大多數(shù)使用場(chǎng)景預(yù)定義了設(shè)置寻拂〕痰欤”
下面介紹一下幾個(gè)類的源碼:
ExecutorService newFixedThreadPool (int nThreads):固定大小線程池。
可以看到祭钉,corePoolSize和maximumPoolSize的大小是一樣的(實(shí)際上瞄沙,后面會(huì)介紹,如果使用無(wú)界queue的話maximumPoolSize參數(shù)是沒(méi)有意義的)慌核,keepAliveTime和unit的設(shè)值表名什么距境?-就是該實(shí)現(xiàn)不想keep alive!最后的BlockingQueue選擇了LinkedBlockingQueue垮卓,該queue有一個(gè)特點(diǎn)垫桂,他是無(wú)界的。