一.線程與進程相關(guān)
1.進程
??定義:進程是具有獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動蒸绩,進程是操作系統(tǒng)分配資源的單位。
??當(dāng)你運行一個程序,你就啟動了一個進程及穗。顯然肝箱,程序只是一組指令的有序集合哄褒,它本身沒有任何運行的含義,只是一個靜態(tài)實體煌张。而進程則不同呐赡,它是程序在某個數(shù)據(jù)集上的執(zhí)行,是一個動態(tài)實體骏融。它因創(chuàng)建而產(chǎn)生链嘀,因調(diào)度而運行萌狂,因等待資源或事件而被處于等待狀態(tài)伦腐,因完成任務(wù)而被撤消腐魂,反映了一個程序在一定的數(shù)據(jù)集上運行的全部動態(tài)過程。
特點:
- 進程是程序的一次執(zhí)行過程禾锤!過程包个!~~活動的刷允。
- 系統(tǒng)資源(如內(nèi)存、文件)以進程為單位分配碧囊。
- 操作系統(tǒng)為每個進程分配了獨立的地址空間树灶。
2.線程
??定義:線程是操作系統(tǒng)能夠進行運算調(diào)度的最小單位。它被包含在進程之中糯而,是進程中的實際運作單位天通。線程是操作系統(tǒng)調(diào)度和分派的基本單位。
??線程是屬于進程的熄驼,線程自己是沒有內(nèi)存空間的像寒,它運行在進程空間內(nèi)。同一進程所產(chǎn)生的線程共享同一內(nèi)存空間瓜贾,當(dāng)進程退出時該進程所產(chǎn)生的線程都會被強制退出并清除诺祸。
??線程可與屬于同一進程的其它線程共享進程所擁有的全部資源,但是其本身基本上不擁有系統(tǒng)資源祭芦,只擁有一點在運行中必不可少的信息(如程序計數(shù)器筷笨、一組寄存器和棧)。
為什么要有線程龟劲?
??首先我們要明確一點胃夏,CPU計算的速度是非常非常快的昌跌,寄存器僅僅能夠追的上他的腳步仰禀,RAM和別的掛在各總線上的設(shè)備更是難以望其項背。因此當(dāng)多個任務(wù)需要執(zhí)行的時候蚕愤,就需要輪流著來悼瘾,同時運行多個進程,即并發(fā)技術(shù)审胸。實現(xiàn)并發(fā)技術(shù)相當(dāng)復(fù)雜亥宿,最容易理解的是“時間片輪轉(zhuǎn)進程調(diào)度“:
??在操作系統(tǒng)的管理下,所有正在運行的進程輪流使用CPU砂沛,每個進程允許占用CPU的時間非常短(比如10毫秒)烫扼,這樣用戶根本感覺不出來CPU是在輪流為多個進程服務(wù),就好象所有的進程都在不間斷地運行一樣碍庵。但實際上在任何一個時間內(nèi)有且僅有一個進程占有CPU映企。
??進程是操作系統(tǒng)分配資源(內(nèi)存空間悟狱,文件)的基本單位,進程所分配的空間在不同的進程之間是相互獨立的堰氓。嘉禾上邊說的“時間片輪轉(zhuǎn)進程調(diào)度”挤渐,可以知道,系統(tǒng)在不同進程之間切換的時候双絮,必然要經(jīng)過“開始執(zhí)行A進程浴麻,保存進程A的上下文,調(diào)入下一個要執(zhí)行的進程B的上下文囤攀,然后開始執(zhí)行進程B,保存進程B的上下文”软免,上下文就是進程所處的環(huán)境,系統(tǒng)切出去執(zhí)行另一個進程之后一段時間要切回來焚挠,而切回來的依據(jù)就是原來進程的所處的環(huán)境(得知道原來那個進程地方在哪膏萧,執(zhí)行到哪了等)。由于A蝌衔,B兩個進程所屬的系統(tǒng)空間榛泛、占用的資源都是相互獨立的,因此這個切換不同進程上下文的過程所消耗的資源就比較大噩斟。而線程是在統(tǒng)一進程內(nèi)部的挟鸠,同一進程不同線程之間共用一段內(nèi)存,貢獻同一資源亩冬,所以線程之間的額切換顯然要比進程之間的切換容易的多。
??同時硼身,一個進程不只是做一個任務(wù)硅急,我呢可能會有不同的任務(wù)需求。比如我打開一個QQ佳遂,可能我一遍下載文件营袜,一遍發(fā)送語音,一遍打字——這里QQ就可以看做是一個進程丑罪,而下文件荚板,發(fā)語音,發(fā)文字是由三個不同的線程完成的吩屹。
總結(jié)一下跪另,引入線程有下面三方面的考慮:
- 應(yīng)用的需要。比如打開一個QQ煤搜,可能我一遍下載文件免绿,一遍發(fā)送語音,一遍打字擦盾。如果QQ是一個進程嘲驾,那么這樣的需求需要線程機制淌哟。
- 開銷的考慮。在進程內(nèi)創(chuàng)建辽故、終止線程比創(chuàng)建徒仓、終止進程要快。同一進程內(nèi)的線程間切換比進程間的切換要快,尤其是用戶級線程間的切換誊垢。線程之間相互通信無須通過內(nèi)核(同一進程內(nèi)的線程共享內(nèi)存和文件)
- 性能的考慮掉弛。多個線程中,任務(wù)功能不同(有的負責(zé)計算彤枢,有的負責(zé)I/O),如果有多個處理器狰晚,一個進程就可以有很多的任務(wù)同時在執(zhí)行。
線程的特點:
- 有自己的棧和棧指針
- 共享所在進程的地址空間和其它資源
- 不運行時需要保存線程上下文環(huán)境(需要程序計數(shù)器等寄存器,和進程一樣缴啡,切回來的時候得知道之前的線程執(zhí)行到哪了)
- 有標(biāo)識符ID(如JAVA中Thread.currentThread())
3.一些通俗的解釋
??上面說了一大堆壁晒,實際上,線程和進程本質(zhì)上是CPU兩種不同的工作時間段的描述业栅,只不過顆粒大小不同秒咐。為什么這么說呢?上面我們反復(fù)強調(diào)過很多遍:進程是操作系統(tǒng)分配資源的基本單位碘裕,線程是操作系統(tǒng)運算調(diào)度的基本單位携取。
??說的跟通俗一點,CPU正真“時間片輪轉(zhuǎn)調(diào)度”的是線程帮孔,真正處理工作的地方也是線程雷滋。但是線程是屬于進程的,加入有兩個進程A和B文兢,每個進程中都有兩個線程A1晤斩,A2,B1姆坚,B2澳泵。CPU的執(zhí)行時間就在A1,A2兼呵,B1兔辅,B2這四個線程之間輪轉(zhuǎn),如果不慎從A1切換到B2击喂,那么也就是進程A切換到了進程B维苔。
從三個角度來剖析二者之間的區(qū)別:
- 調(diào)度:線程作為調(diào)度和分配的基本單位,進程作為擁有資源的基本單位懂昂。
- 并發(fā)性:不僅進程之間可以并發(fā)執(zhí)行蕉鸳,同一個進程的多個線程之間也可以并發(fā)執(zhí)行。
- 擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統(tǒng)資源潮尝,但可以訪問隸屬于進程的資源榕吼。
4.Android中的線程與進程
??在Android系統(tǒng)中,每一個App都是一個Linux用戶勉失。一般情況下羹蚣,每個App都是運行在一個進程的一個線程中,這個線程習(xí)慣稱為主線程或者UI線程(注意乱凿,一個進程的一個線程之中)顽素。
??Zygote是一個虛擬機進程,同時也是一個虛擬機實例的孵化器徒蟆,每當(dāng)系統(tǒng)要求執(zhí)行一個 Android應(yīng)用程序胁出,Zygote就會FORK出一個子進程來執(zhí)行該應(yīng)用程序。
??Zygote進程是在系統(tǒng)啟動時產(chǎn)生的段审,它會完成虛擬機的初始化全蝶,庫的加載,預(yù)置類庫的加載和初始化等等操作寺枉,而在系統(tǒng)需要一個新的虛擬機實例時抑淫,Zygote通過復(fù)制自身,最快速的提供個系統(tǒng)姥闪。
以上內(nèi)容參考:
騰訊面試題04.進程和線程的區(qū)別始苇?
android 線程與進程 區(qū)別 聯(lián)系
線程和進程的區(qū)別是什么? 知乎.zhonyong的回答
二.線程與線程池
1.線程
??Java中有兩種創(chuàng)建線程的方式筐喳,即我們所熟知的繼承thread類與實現(xiàn)Runnable接口催式。于是乎就來了一個非常“經(jīng)典”并且并用爛了的例子——買票避归!這里我們也展示一下:
①繼承Thread類:
public class TicketThread extends Thread{
private int ticket = 10;
private String name;
public TicketThread(String name){
this.name =name;
}
public void run(){
for(int i =0;i<500;i++){
if(this.ticket>0){
System.out.println(this.name+"賣票---->"+(this.ticket--));
}
}
}
public static class ThreadDemo {
public static void main(String[] args) {
TicketThread mt1= new TicketThread("一號窗口");
TicketThread mt2= new TicketThread("二號窗口");
TicketThread mt3= new TicketThread("三號窗口");
mt1.start();
mt2.start();
mt3.start();
}
}
}
結(jié)果是:
一號窗口賣票---->10
一號窗口賣票---->9
一號窗口賣票---->8
三號窗口賣票---->10
二號窗口賣票---->10
二號窗口賣票---->9
二號窗口賣票---->8
二號窗口賣票---->7
二號窗口賣票---->6
二號窗口賣票---->5
......
后面的一串結(jié)果我就不貼了荣月,意思就是說,票被賣重復(fù)了槐脏,每張票都賣了三遍。
②實現(xiàn)Runnable接口:
public class TicketRunnable implements Runnable{
private int ticket =10;
private String name;
@Override
public void run() {
// TODO Auto-generated method stub
for(int i =0;i<500;i++){
if(this.ticket>0){
System.out.println(Thread.currentThread().getName()+"賣票---->"+(this.ticket--));
}
}
}
public static class RunnableDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//設(shè)計三個線程
TicketRunnable mt = new TicketRunnable();
Thread t1 = new Thread(mt,"一號窗口");
Thread t2 = new Thread(mt,"二號窗口");
Thread t3 = new Thread(mt,"三號窗口");
t1.start();
t2.start();
t3.start();
}
}
}
結(jié)果為:
二號窗口賣票---->10
二號窗口賣票---->8
三號窗口賣票---->9
二號窗口賣票---->7
三號窗口賣票---->6
二號窗口賣票---->5
二號窗口賣票---->3
二號窗口賣票---->2
三號窗口賣票---->4
二號窗口賣票---->1
結(jié)果剛好撇寞,每張票賣一次顿天。
??于是就有博客說了,上面兩種實現(xiàn)方式蔑担,繼承Thread類是各自線程賣三份票牌废,會把票賣重復(fù)了;實現(xiàn)Runnable接口是三個線程賣同一份票啤握,所以結(jié)果正確——這說了好像跟沒說一樣鸟缕??!更有甚者說懂从,第一種方法中“保證安全的方法:把賣票的步驟用synchronized包起來授段。那么就不會出問題了”——你在逗我?番甩?侵贵!
??好吧~~我們來看看這兩種方法——事實上,不論是繼承Thread類還是實現(xiàn)Runnable接口缘薛,其本質(zhì)都要:①重寫Runnale接口中的Run方法窍育,在其中定義我們在線程中具體要做的事情。②調(diào)用Thread.start()方法從系統(tǒng)中new一個線程出來宴胧。
我們可以看下漱抓。不信你回過頭去看看上面兩端代碼,都做了這兩件事情恕齐。
??我們可以看下Thread類源碼:
public class Thread implements Runnable {
看到了吧乞娄?Thread類也實現(xiàn)了Runnable接口,而Runnable接口:
public interface Runnable {
public abstract void run();
}
就兩句代碼檐迟,也就是抽象的run()方法补胚,所以無論你是繼承的Thread類還是直接實現(xiàn)的Runnable方法,實際上最終都要重寫其中的run方法追迟。我們回到Thread類中溶其,看看我們的new Thread()也就是構(gòu)造函數(shù):
public Thread(String name) { //上述第一種方法
init(null, null, name, 0);
}
public Thread(Runnable target, String name) { //上述第二種方法
init(null, target, name, 0);
}
我們主要要看到這個target就是我們傳進去的Runnable對象。我們接下來直接看start()方法敦间。
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
started = false;
try {
nativeCreate(this, stackSize, daemon);
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
這個段代碼中nativeCreate(this, stackSize, daemon);
這句代碼就是向系統(tǒng)請求創(chuàng)建一個線程的方法瓶逃,這是一個native方法,我們不作分析廓块。然后再看我們都要重寫的run方法:
@Override
public void run() {
if (target != null) {
target.run();
}
}
看到了吧厢绝?我沒騙你吧~~重寫的run方法中調(diào)用了target.run();
,也就是我們new Thread(mt,"一號窗口");
穿進去的mt這個Runnable對象带猴。
??所以昔汉,顯而易見,上述兩個結(jié)論是正確的:①重寫Runnale接口中的Run方法拴清,在其中定義我們在線程中具體要做的事情靶病。②調(diào)用Thread.start()方法從系統(tǒng)中new一個線程出來。
我們可以看下口予。
??至于為什么第一種情況會出現(xiàn)票重復(fù)賣的情況而第二種沒有呢娄周?這主要是因為,第一種情況中:
public class TicketThread extends Thread{
private int ticket = 10;
這個ticket是TicketThread類的實例變量沪停,而我們在start這個線程的時候煤辨,通過new TicketThread("一號窗口");
裳涛,new TicketThread("二號窗口");
,TicketThread mt3= new TicketThread("三號窗口");
看到?jīng)]有众辨,這里每new一個TicketThread對象端三,都會把該類中的實例變量拷貝一份到自己的內(nèi)存中,new了三次泻轰,也就拷貝了三份ticket到三個對象中技肩,然后start之后當(dāng)然是每個線程跑自己線程中的ticket,所以就出現(xiàn)跑重復(fù)了浮声;
??如果我們把ticket聲明為static類型虚婿,即private static int ticket = 10;
,再跑一遍泳挥,結(jié)果就和第二種情況一樣了然痊!這是因為,靜態(tài)成員是屬于整個類的屉符,不是屬于對象的剧浸。類加載的時候,JAW就會給靜態(tài)成員分配一個特定的內(nèi)存空間矗钟,所有之后取用這個靜態(tài)成員的時候唆香,都會去這個特定的內(nèi)存中取用(保證了可見性),并不會存在拷貝值的問題吨艇,因此就不會出錯了躬它。
??對于第二種情況:
public class TicketRunnable implements Runnable{
private int ticket =10;
這里ticket是TicketRunnable類的實例變量,而下面在start()的時候东涡,寫法為:
TicketRunnable mt = new TicketRunnable();
Thread t1 = new Thread(mt,"一號窗口");
Thread t2 = new Thread(mt,"二號窗口");
Thread t3 = new Thread(mt,"三號窗口");
可到?jīng)]有冯吓,TicketRunnable類只被new了一次,那具體使用的過程中ticket自然也就只有一份了疮跑。如果我們把這里改成:
TicketRunnable mt1 = new TicketRunnable();
TicketRunnable mt2 = new TicketRunnable();
TicketRunnable mt3 = new TicketRunnable();
Thread t1 = new Thread(mt1,"一號窗口");
Thread t2 = new Thread(mt2,"二號窗口");
Thread t3 = new Thread(mt3,"三號窗口");
運行一下组贺,結(jié)果就和第一種情況一樣,每張票被賣了三次祖娘,這是因為失尖,new了三次TicketRunnable,ticket被拷貝了三次渐苏。
那么我們在使用中到底是繼承Thread還是實現(xiàn)Runnable接口呢掀潮?因為Java不支持類的多重繼承,但允許你調(diào)用多個接口整以。所以如果你要繼承其他類胧辽,當(dāng)然是調(diào)用Runnable接口更好了峻仇。一般我們在新建一個線程的時候公黑,直接
new Thread(new Runnable() {
@Override
public void run() {
//do sth .
}
}).start();
就可以了,簡潔明了。
2.線程池
1)Java中創(chuàng)建線程的第三種方式——Callable+FutureTask+ExecutorService
??我們在上面講的創(chuàng)建線程的兩種方式凡蚜,都存在一個缺陷就是:在執(zhí)行完之后人断,無法直接獲取執(zhí)行的結(jié)果。如果需要獲取結(jié)果朝蜘,就需要通過共享變量或者線程間通信的方式來達到效果恶迈,這顯然比較麻煩。而我們現(xiàn)在介紹的Callable+FutureTask的方式谱醇,則能很輕松很隨意的實現(xiàn)結(jié)果的獲取暇仲。
①Callable與Runnable
??Runnable方法我們之前說過他的使用,這里在貼一遍源碼副渴,我們知道奈附,這個run是我們要在程序中手動重寫的,里邊寫的是我們要具體做的事情煮剧,而且這個run方法是void類型的斥滤,也就是說我們執(zhí)行完了之后無法獲取結(jié)果。
public interface Runnable {
public abstract void run();
}
我們再來看Callable:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
可以看到這是一個泛型接口勉盅,其中call()方法的返回值就是我們傳進來的泛型V佑颇。而且,這里的call方法和上面的run()方法一樣草娜,也是需要我們在程序中手動重寫的挑胸,其中寫我們具體的要做的事情;不同的是驱还,這里的call方法是需要return的嗜暴。
??Callable一般配合ExecutorService類來使用,我們之后會通過實例展示它的使用:
<T> Future<T> submit(Callable<T> task);
②Future接口與FutureTask類
??首先,F(xiàn)uture是一個接口议蟆,他當(dāng)中封裝了幾個必要的方法:
//方法用來取消任務(wù)闷沥,如果取消任務(wù)成功則返回true,如果取消任務(wù)失敗則返回false咐容。參數(shù)mayInterruptIfRunning表示
//是否允許取消正在執(zhí)行卻沒有執(zhí)行完畢的任務(wù)舆逃,如果設(shè)置true,則表示可以取消正在執(zhí)行過程中的任務(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。
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled(); //表示任務(wù)是否被取消成功奔誓,如果在任務(wù)正常完成前被取消成功斤吐,則返回 true。
boolean isDone(); //表示任務(wù)是否已經(jīng)完成厨喂,若任務(wù)完成和措,則返回true;
V get() throws InterruptedException, ExecutionException; //方法用來獲取執(zhí)行結(jié)果蜕煌,這個方法會產(chǎn)生阻塞臼婆,會一直等到任務(wù)執(zhí)行完畢才返回;
V get(long timeout, TimeUnit unit) //用來獲取執(zhí)行結(jié)果幌绍,如果在指定時間內(nèi)颁褂,還沒獲取到結(jié)果,就直接返回null傀广。
throws InterruptedException, ExecutionException, TimeoutException;
也就是說Future提供了三種功能:
- 判斷任務(wù)是否完成颁独;
- 能夠中斷任務(wù);
- 能夠獲取任務(wù)執(zhí)行結(jié)果伪冰。
因為Future只是一個接口誓酒,所以是無法直接用來創(chuàng)建對象使用的,因此就有了下面的FutureTask贮聂。
??FutureTask是一個具體類靠柑,實現(xiàn)了RunnableFuture接口,而RunnableFuture接口實現(xiàn)了Runnable和Future<V>接口:
public class FutureTask<V> implements RunnableFuture<V> {
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
FutureTask類的兩個構(gòu)造器:
public FutureTask(Callable<V> callable) {
public FutureTask(Runnable runnable, V result) {
事實上吓懈,F(xiàn)utureTask是Future接口的唯一實現(xiàn)類歼冰。
③舉個栗子
??使用Callable+FutureTask獲取執(zhí)行結(jié)果:
public class FutureTaskThread {
public static void main(String[] args) {
//第一種方式,使用線程池,即ExecutorService
ExecutorService executor = Executors.newCachedThreadPool();
Task task = new Task();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
executor.submit(futureTask);
executor.shutdown();
//第二種方式耻警,注意這種方式和第一種方式效果是類似的隔嫡,只不過一個使用的是ExecutorService,一個使用的是Thread
/*Task task = new Task();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
Thread thread = new Thread(futureTask);
thread.start();
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("主線程:"+Thread.currentThread().getName());
try {
System.out.println("task運行結(jié)果"+futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("所有任務(wù)執(zhí)行完畢");
}
}
class Task implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("子線程:"+Thread.currentThread().getName());
Thread.sleep(3000);
int sum = 0;
for(int i=0;i<100;i++)
sum += i;
return sum;
}
}
打印結(jié)果為:
子線程:pool-1-thread-1
主線程:main
task運行結(jié)果4950
所有任務(wù)執(zhí)行完畢
可以看到甘穿,ExecutorService executor = Executors.newCachedThreadPool();
這里我們先從線程池中拿出一個線程腮恩,然后FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
新建一個任務(wù),再將這個任務(wù)加入到線程池中去跑executor.submit(futureTask);
温兼,跑完之后executor.shutdown();
關(guān)閉線程池秸滴,并通過futureTask.get()
方法來獲取跑完之后的結(jié)果。從結(jié)果來看募判,打印線程名——子線程:pool-1-thread-1荡含,主線程:main吝羞,顯而易見子線程是在線程池中跑的。
??這里我們需要強調(diào)的一點是内颗,上面的例子中,Callable+FutureTask只是創(chuàng)建一個能夠獲取執(zhí)行結(jié)果的任務(wù)敦腔,真正創(chuàng)建線程的地方是在ExecutorService線程池中均澳。
??如果我們換一種方式,用new Thread來替換線程池符衔,也就是上面注釋掉的第二種方法找前,運行結(jié)果為:
子線程:Thread-0
主線程:main
task運行結(jié)果4950
所有任務(wù)執(zhí)行完畢
可以看到,執(zhí)行結(jié)果完全一樣判族,只不過子線程的線程名是“Thread-0”躺盛,而不是線程池了。這里我們已經(jīng)引入了線程池的概念形帮,那我們接下來就說說線程池的那些事槽惫。
2)Executor框架與線程池
??上面說了創(chuàng)建一般線程的方法,new Thread(new Runnable() {
辩撑,這種方法在線程并發(fā)不多的程序中確實不錯界斜,但是如果出現(xiàn)高并發(fā)需要大量創(chuàng)建線程的情況下,勁導(dǎo)致系統(tǒng)的性能變的非常糟糕合冀,主要因為:
- 線程的創(chuàng)建和銷毀都需要時間各薇,當(dāng)有大量的線程創(chuàng)建和銷毀時,那么這些時間的消耗則比較明顯君躺,將導(dǎo)致性能上的缺失
- 大量的線程創(chuàng)建峭判、執(zhí)行和銷毀是非常耗cpu和內(nèi)存的,這樣將直接影響系統(tǒng)的吞吐量棕叫,導(dǎo)致性能急劇下降林螃,如果內(nèi)存資源占用的比較多,還很可能造成OOM
- 大量的線程的創(chuàng)建和銷毀很容易導(dǎo)致GC頻繁的執(zhí)行俺泣,從而發(fā)生內(nèi)存抖動現(xiàn)象治宣,而發(fā)生了內(nèi)存抖動,對于移動端來說砌滞,最大的影響就是造成界面卡頓
??這個時候侮邀,就要用到線程池(ThreadPoolExecutor)了。線程池的基本思想還是一種對象池的思想贝润,開辟一塊內(nèi)存空間绊茧,里面存放了眾多(未死亡)的線程,池中線程執(zhí)行調(diào)度由池管理器來處理打掘。當(dāng)有線程任務(wù)時华畏,從池中取一個鹏秋,執(zhí)行完成后線程對象歸池,這樣可以避免反復(fù)創(chuàng)建線程對象所帶來的性能開銷亡笑,節(jié)省了系統(tǒng)的資源侣夷。
①Executor接口與ExecutorService接口
??首先,這是兩個接口:
public interface Executor {
void execute(Runnable command);
}
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
......
}
可以看到仑乌,ExecutorService接口繼承自Executor接口百拓。Executor接口中只定義了一個方法execute(Runnable command),該方法接收一個Runable實例晰甚,它用來執(zhí)行一個任務(wù)衙传。這個任務(wù)就是一個實現(xiàn)了Runnable接口的類。
??ExecutorService繼承自Executor接口厕九,再此基礎(chǔ)之上實現(xiàn)了更加豐富的實現(xiàn)多線程的方法蓖捶,如shutdown(),submit()等扁远。調(diào)用ExecutorService的shutdown()方法來平滑地關(guān)閉 ExecutorService——調(diào)用該方法后俊鱼,將導(dǎo)致ExecutorService停止接受任何新的任務(wù)且等待已經(jīng)提交的任務(wù)執(zhí)行完成(已經(jīng)提交的任務(wù)會分兩類:一類是已經(jīng)在執(zhí)行的,另一類是還沒有開始執(zhí)行的)畅买,當(dāng)所有已經(jīng)提交的任務(wù)執(zhí)行完畢后將會關(guān)閉ExecutorService亭引。因此我們一般用該接口來實現(xiàn)和管理多線程。
??ExecutorService的生命周期包括三種狀態(tài):運行皮获、關(guān)閉焙蚓、終止。創(chuàng)建后便進入運行狀態(tài)洒宝,當(dāng)調(diào)用了shutdown()方法時购公,便進入關(guān)閉狀態(tài),此時意味著ExecutorService不再接受新的任務(wù)雁歌,但它還在執(zhí)行已經(jīng)提交了的任務(wù)宏浩,當(dāng)所有已經(jīng)提交了的任務(wù)執(zhí)行完后,便到達終止?fàn)顟B(tài)靠瞎。如果不調(diào)用shutdown()方法比庄,ExecutorService會一直處在運行狀態(tài),不斷接收新的任務(wù)乏盐,執(zhí)行新的任務(wù)佳窑,服務(wù)器端一般不需要關(guān)閉它,保持一直運行即可父能。
②Executors類與ThreadPoolExecutor類
??首先這是兩個類神凑,注意Executors類與Executor接口,多了一個s,不要搞混了。Executors類是一個很單純的類溉委,他沒有實現(xiàn)任何接口鹃唯,也沒有繼承任何父類:
public class Executors {
他的作用是,通過一系列工廠方法用于創(chuàng)建線程池瓣喊,也就是new ThreadPoolExecutor
類坡慌,我們可以看下ThreadPoolExecutor類:
public class ThreadPoolExecutor extends AbstractExecutorService {
public abstract class AbstractExecutorService implements ExecutorService {
可以看到,ThreadPoolExecutor繼承自AbstractExecutorService類藻三,但是AbstractExecutorService類實現(xiàn)了ExecutorService接口洪橘,所以相當(dāng)于ThreadPoolExecutor實現(xiàn)了ExecutorService接口。因此趴酣,我們可以通過ExecutorService executor = Executors.newCachedThreadPool();
這種方式來創(chuàng)建線程池。
??Executors類中有一下幾種常用的創(chuàng)建線程池的方法:;
public static ExecutorService newFixedThreadPool(int nThreads)
創(chuàng)建固定數(shù)目線程的線程池坑夯。
public static ExecutorService newCachedThreadPool()
創(chuàng)建一個可緩存的線程池岖寞,調(diào)用execute將重用以前構(gòu)造的線程(如果線程可用)。如果現(xiàn)有線程沒有可用的柜蜈,則創(chuàng)建一個
新線程并添加到池中仗谆。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。
public static ExecutorService newSingleThreadExecutor()
創(chuàng)建一個單線程化的Executor淑履。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
創(chuàng)建一個支持定時及周期性的任務(wù)執(zhí)行的線程池隶垮,多數(shù)情況下可用來替代Timer類。
??一般來說秘噪,CachedTheadPool在程序執(zhí)行過程中通常會創(chuàng)建與所需數(shù)量相同的線程狸吞,然后在它回收舊線程時停止創(chuàng)建新線程,因此它是合理的Executor的首選指煎,只有當(dāng)這種方式會引發(fā)問題時(比如需要大量長時間面向連接的線程時)蹋偏,才需要考慮用FixedThreadPool。(該段話摘自《Thinking in Java》第四版)
線程池內(nèi)部實現(xiàn)原理比較復(fù)雜至壤,我們這里不對其做深究威始,我們目前只需要掌握它的用法:
③Executor執(zhí)行Runnable任務(wù)
??通過Executors的以上四個靜態(tài)工廠方法獲得 ExecutorService實例,而后調(diào)用該實例的execute(Runnable command)方法即可像街。一旦Runnable任務(wù)傳遞到execute()方法黎棠,該方法便會自動在一個線程上執(zhí)行。下面是是Executor執(zhí)行Runnable任務(wù)的示例代碼:
public class CachedThreadPoolRunnable {
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
// ExecutorService executorService = Executors.newFixedThreadPool(5);
// ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++){
executorService.execute(new TestRunnable());
System.out.println(" a" + i );
}
executorService.shutdown();
}
}
class TestRunnable implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName() + "線程被調(diào)用了");
}
}
運行結(jié)果為:
a0
pool-1-thread-1線程被調(diào)用了
a1
a2
pool-1-thread-2線程被調(diào)用了
pool-1-thread-1線程被調(diào)用了
a3
a4
pool-1-thread-1線程被調(diào)用了
pool-1-thread-3線程被調(diào)用了
可以看到镰绎,pool-1-thread-1這條線程被執(zhí)行了三次脓斩,這說明:①線程池中線程的使用是隨機的,execute會首先在線程池中選擇一個已有空閑線程來執(zhí)行任務(wù)畴栖,如果線程池中沒有空閑線程俭厚,它便會創(chuàng)建一個新的線程來執(zhí)行任務(wù)。②通過Executors.newCachedThreadPool();
這種方式來創(chuàng)建的線程池是可以緩存其中的線程并重復(fù)利用的驶臊。
??如果我們把上面代碼中Executors.newCachedThreadPool();
這種方式換成Executors.newFixedThreadPool(5);
這種方式挪挤,得到結(jié)果為:
a0
pool-1-thread-1線程被調(diào)用了
a1
a2
a3
pool-1-thread-2線程被調(diào)用了
a4
pool-1-thread-4線程被調(diào)用了
pool-1-thread-3線程被調(diào)用了
pool-1-thread-5線程被調(diào)用了
可以看到叼丑,沒有線程被復(fù)用,全部都是新創(chuàng)建的線程扛门。
??再換成Executors.newSingleThreadExecutor();
這種方式鸠信,可以看到谜慌,只有一條線程了:
a0
a1
a2
pool-1-thread-1線程被調(diào)用了
pool-1-thread-1線程被調(diào)用了
pool-1-thread-1線程被調(diào)用了
pool-1-thread-1線程被調(diào)用了
a3
a4
pool-1-thread-1線程被調(diào)用了
還有一點扁达,由于上面是通過Runnable這種方式實現(xiàn)的寸宵,因此最后執(zhí)行的結(jié)果不能直接返回又谋,下面我們來看Callable這種方式:
④Executor執(zhí)行Callable任務(wù)
??在Java 5之后靡努,任務(wù)分兩類:一類是實現(xiàn)了Runnable接口的類突琳,一類是實現(xiàn)了Callable接口的類键菱。兩者都可以被ExecutorService執(zhí)行顺饮,但是Runnable任務(wù)沒有返回值火焰,而Callable任務(wù)有返回值劲装。并且Callable的call()方法只能通過ExecutorService的submit(Callable<T> task) 方法來執(zhí)行,并且返回一個 <T>Future<T>昌简,是表示任務(wù)等待完成的 Future占业。
??當(dāng)將一個Callable的對象傳遞給ExecutorService的submit方法,則該call方法自動在一個線程上執(zhí)行纯赎,并且會返回執(zhí)行結(jié)果Future對象,在該Future對象上調(diào)用get方法谦疾,將返回程序執(zhí)行的結(jié)果。同樣犬金,將Runnable的對象傳遞給ExecutorService的submit方法念恍,則該run方法自動在一個線程上執(zhí)行,并且會返回執(zhí)行結(jié)果Future對象晚顷,但是在該Future對象上調(diào)用get方法樊诺,將返回null。
下面給出一個Executor執(zhí)行Callable任務(wù)的示例代碼:
public class ExecutorCallable {
public static void main(String[] args){
ExecutorService executorService = Executors.newCachedThreadPool();
List<Future<String>> resultList = new ArrayList<Future<String>>();
//創(chuàng)建10個任務(wù)并執(zhí)行
for (int i = 0; i < 10; i++){
//使用ExecutorService執(zhí)行Callable類型的任務(wù)音同,并將結(jié)果保存在future變量中
Future<String> future = executorService.submit(new TaskResultCallable(i));
//將任務(wù)執(zhí)行結(jié)果存儲到List中
resultList.add(future);
}
//遍歷任務(wù)的結(jié)果
for (Future<String> fs : resultList){
try{
while(!fs.isDone());//Future返回如果沒有完成词爬,則一直循環(huán)等待,直到Future返回完成
System.out.println("任務(wù)返回結(jié)果輸出:"+fs.get()); //打印各個線程(任務(wù))執(zhí)行的結(jié)果
}catch(InterruptedException e){
e.printStackTrace();
}catch(ExecutionException e){
e.printStackTrace();
}finally{
//啟動一次順序關(guān)閉权均,執(zhí)行以前提交的任務(wù)顿膨,但不接受新任務(wù)
executorService.shutdown();
}
}
}
}
class TaskResultCallable implements Callable<String>{
private int id;
public TaskResultCallable(int id){
this.id = id;
}
/**
* 任務(wù)的具體過程,一旦任務(wù)傳給ExecutorService的submit方法叽赊,
* 則該方法自動在一個線程上執(zhí)行
*/
public String call() throws Exception {
System.out.println("子線程 :" + Thread.currentThread().getName());
//該返回結(jié)果將被Future的get方法得到
return "call()方法被自動調(diào)用恋沃,任務(wù)返回的結(jié)果是:" + id + " " + Thread.currentThread().getName();
}
}
輸出結(jié)果為:
子線程 :pool-1-thread-2
子線程 :pool-1-thread-1
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用,任務(wù)返回的結(jié)果是:0 pool-1-thread-1
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用必指,任務(wù)返回的結(jié)果是:1 pool-1-thread-2
子線程 :pool-1-thread-3
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用囊咏,任務(wù)返回的結(jié)果是:2 pool-1-thread-3
子線程 :pool-1-thread-4
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用,任務(wù)返回的結(jié)果是:3 pool-1-thread-4
子線程 :pool-1-thread-5
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用,任務(wù)返回的結(jié)果是:4 pool-1-thread-5
子線程 :pool-1-thread-7
子線程 :pool-1-thread-6
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用梅割,任務(wù)返回的結(jié)果是:5 pool-1-thread-6
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用霜第,任務(wù)返回的結(jié)果是:6 pool-1-thread-7
子線程 :pool-1-thread-8
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用,任務(wù)返回的結(jié)果是:7 pool-1-thread-8
子線程 :pool-1-thread-9
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用户辞,任務(wù)返回的結(jié)果是:8 pool-1-thread-9
子線程 :pool-1-thread-10
任務(wù)返回結(jié)果輸出:call()方法被自動調(diào)用泌类,任務(wù)返回的結(jié)果是:9 pool-1-thread-10
可以看到,你在callable中的return結(jié)果底燎,就是future.get()
中得到的結(jié)果刃榨。
站在巨人的肩膀上摘蘋果:
【Java并發(fā)編程】之十九:并發(fā)新特性—Executor框架與線程池(含代碼)
Java并發(fā)編程:Callable、Future和FutureTask