線程與進(jìn)程
線程:進(jìn)程中負(fù)責(zé)程序執(zhí)行的執(zhí)行單元
線程本身依靠程序進(jìn)行運(yùn)行
線程是程序中的順序控制流暗赶,只能使用分配給程序的資源和環(huán)境
進(jìn)程:執(zhí)行中的程序
一個(gè)進(jìn)程至少包含一個(gè)線程
單線程:程序中只存在一個(gè)線程尾菇,實(shí)際上主方法就是一個(gè)主線程
多線程:在一個(gè)程序中運(yùn)行多個(gè)任務(wù)
目的是更好地使用CPU資源
線程的實(shí)現(xiàn)
繼承Thread類
在java.lang
包中定義, 繼承Thread類必須重寫run()
方法
class MyThread extends Thread{
private static int num = 0;
public MyThread(){
num++;
}
@Override
public void run() {
System.out.println("主動(dòng)創(chuàng)建的第"+num+"個(gè)線程");
}
}
創(chuàng)建好了自己的線程類之后垫桂,就可以創(chuàng)建線程對象了胧弛,然后通過start()方法去啟動(dòng)線程妇斤。注意橙困,不是調(diào)用run()方法啟動(dòng)線程驾霜,run方法中只是定義需要執(zhí)行的任務(wù),如果調(diào)用run方法店枣,即相當(dāng)于在主線程中執(zhí)行run方法速警,跟普通的方法調(diào)用沒有任何區(qū)別叹誉,此時(shí)并不會(huì)創(chuàng)建一個(gè)新的線程來執(zhí)行定義的任務(wù)。
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread{
private static int num = 0;
public MyThread(){
num++;
}
@Override
public void run() {
System.out.println("主動(dòng)創(chuàng)建的第"+num+"個(gè)線程");
}
}
在上面代碼中闷旧,通過調(diào)用start()方法长豁,就會(huì)創(chuàng)建一個(gè)新的線程了。為了分清start()方法調(diào)用和run()方法調(diào)用的區(qū)別忙灼,請看下面一個(gè)例子:
public class Test {
public static void main(String[] args) {
System.out.println("主線程ID:"+Thread.currentThread().getId());
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.run();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("name:"+name+" 子線程ID:"+Thread.currentThread().getId());
}
}
運(yùn)行結(jié)果:
從輸出結(jié)果可以得出以下結(jié)論:
1)thread1和thread2的線程ID不同匠襟,thread2和主線程ID相同,
說明通過run方法調(diào)用并不會(huì)創(chuàng)建新的線程该园,而是在主線程中直接運(yùn)行run方法酸舍,跟普通的方法調(diào)用沒有任何區(qū)別
;2)雖然thread1的start方法調(diào)用在thread2的run方法前面調(diào)用里初,但是先輸出的是thread2的run方法調(diào)用的相關(guān)信息啃勉,說明
新線程創(chuàng)建的過程不會(huì)阻塞主線程的后續(xù)執(zhí)行
。
實(shí)現(xiàn)Runnable接口
在Java中創(chuàng)建線程除了繼承Thread類之外双妨,還可以通過實(shí)現(xiàn)Runnable接口來實(shí)現(xiàn)類似的功能淮阐。實(shí)現(xiàn)Runnable接口必須重寫其run方法。
下面是一個(gè)例子:
public class Test {
public static void main(String[] args) {
System.out.println("主線程ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}
@Override
public void run() {
System.out.println("子線程ID:"+Thread.currentThread().getId());
}
}
Runnable的中文意思是“任務(wù)”刁品,顧名思義泣特,通過實(shí)現(xiàn)Runnable接口,我們定義了一個(gè)子任務(wù)哑诊,然后將子任務(wù)交由Thread去執(zhí)行群扶。注意,這種方式必須將Runnable作為Thread類的參數(shù)镀裤,然后通過Thread的start方法來創(chuàng)建一個(gè)新線程來執(zhí)行該子任務(wù)竞阐。如果調(diào)用Runnable的run方法的話,是不會(huì)創(chuàng)建新線程的暑劝,這根普通的方法調(diào)用沒有任何區(qū)別骆莹。
事實(shí)上,查看Thread類的實(shí)現(xiàn)源代碼會(huì)發(fā)現(xiàn)Thread類是實(shí)現(xiàn)了Runnable接口的担猛。
在Java中幕垦,這2種方式都可以用來創(chuàng)建線程去執(zhí)行子任務(wù),具體選擇哪一種方式要看自己的需求傅联。直接繼承Thread類的話先改,可能比實(shí)現(xiàn)Runnable接口看起來更加簡潔,但是由于Java只允許單繼承蒸走,所以如果自定義類需要繼承其他類仇奶,則只能選擇實(shí)現(xiàn)Runnable接口。
使用ExecutorService比驻、Callable该溯、Future實(shí)現(xiàn)有返回結(jié)果的多線程
ExecutorService岛抄、Callable、Future這個(gè)對象實(shí)際上都是屬于Executor框架中的功能類狈茉。想要詳細(xì)了解Executor框架的可以訪問http://www.javaeye.com/topic/366591 夫椭,這里面對該框架做了很詳細(xì)的解釋。返回結(jié)果的線程是在JDK1.5中引入的新特征氯庆,確實(shí)很實(shí)用蹭秋,有了這種特征我就不需要再為了得到返回值而大費(fèi)周折了,而且即便實(shí)現(xiàn)了也可能漏洞百出点晴。
可返回值的任務(wù)必須實(shí)現(xiàn)Callable接口感凤,類似的悯周,無返回值的任務(wù)必須Runnable接口粒督。執(zhí)行Callable任務(wù)后,可以獲取一個(gè)Future的對象禽翼,在該對象上調(diào)用get就可以獲取到Callable任務(wù)返回的Object了屠橄,再結(jié)合線程池接口ExecutorService就可以實(shí)現(xiàn)傳說中有返回結(jié)果的多線程了。下面提供了一個(gè)完整的有返回結(jié)果的多線程測試?yán)尤虻玻贘DK1.5下驗(yàn)證過沒問題可以直接使用锐墙。代碼如下:
/**
* 有返回值的線程
*/
@SuppressWarnings("unchecked")
public class Test {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println("----程序開始運(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對象
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對象上獲取任務(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 + "毫秒】";
}
}
代碼說明:
上述代碼中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)有線程沒有可用的蚀乔,則創(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ù)情況下可用來替代Timer類婉弹。
ExecutoreService提供了submit()方法睬魂,傳遞一個(gè)Callable,或Runnable镀赌,返回Future氯哮。如果Executor后臺(tái)線程池還沒有完成Callable的計(jì)算,這調(diào)用返回Future對象的get()方法佩脊,會(huì)阻塞直到計(jì)算完成蛙粘。
線程的狀態(tài)
在正式學(xué)習(xí)Thread類中的具體方法之前垫卤,我們先來了解一下線程有哪些狀態(tài),這個(gè)將會(huì)有助于后面對Thread類中的方法的理解出牧。
創(chuàng)建(new)狀態(tài): 準(zhǔn)備好了一個(gè)多線程的對象
就緒(runnable)狀態(tài): 調(diào)用了
start()
方法, 等待CPU進(jìn)行調(diào)度運(yùn)行(running)狀態(tài): 執(zhí)行
run()
方法阻塞(blocked)狀態(tài): 暫時(shí)停止執(zhí)行, 可能將資源交給其它線程使用
終止(dead)狀態(tài): 線程銷毀
當(dāng)需要新起一個(gè)線程來執(zhí)行某個(gè)子任務(wù)時(shí)穴肘,就創(chuàng)建了一個(gè)線程。但是線程創(chuàng)建之后舔痕,不會(huì)立即進(jìn)入就緒狀態(tài)评抚,因?yàn)榫€程的運(yùn)行需要一些條件(比如內(nèi)存資源,在前面的JVM內(nèi)存區(qū)域劃分一篇博文中知道程序計(jì)數(shù)器伯复、Java棧慨代、本地方法棧都是線程私有的,所以需要為線程分配一定的內(nèi)存空間)啸如,只有線程運(yùn)行需要的所有條件滿足了侍匙,才進(jìn)入就緒狀態(tài)。
當(dāng)線程進(jìn)入就緒狀態(tài)后叮雳,不代表立刻就能獲取CPU執(zhí)行時(shí)間想暗,也許此時(shí)CPU正在執(zhí)行其他的事情,因此它要等待帘不。當(dāng)?shù)玫紺PU執(zhí)行時(shí)間之后说莫,線程便真正進(jìn)入運(yùn)行狀態(tài)。
線程在運(yùn)行狀態(tài)過程中寞焙,可能有多個(gè)原因?qū)е庐?dāng)前線程不繼續(xù)運(yùn)行下去储狭,比如用戶主動(dòng)讓線程睡眠(睡眠一定的時(shí)間之后再重新執(zhí)行)、用戶主動(dòng)讓線程等待捣郊,或者被同步塊給阻塞辽狈,此時(shí)就對應(yīng)著多個(gè)狀態(tài):time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)模她、blocked(阻塞)稻艰。
當(dāng)由于突然中斷或者子任務(wù)執(zhí)行完畢,線程就會(huì)被消亡侈净。
下面這副圖描述了線程從創(chuàng)建到消亡之間的狀態(tài):
在有些教程上將blocked尊勿、waiting、time waiting統(tǒng)稱為阻塞狀態(tài)畜侦,這個(gè)也是可以的元扔,只不過這里我想將線程的狀態(tài)和Java中的方法調(diào)用聯(lián)系起來,所以將waiting和time waiting兩個(gè)狀態(tài)分離出來旋膳。
注:sleep和wait的區(qū)別:
sleep
是Thread
類的方法,wait
是Object
類中定義的方法.
Thread.sleep
不會(huì)導(dǎo)致鎖行為的改變, 如果當(dāng)前線程是擁有鎖的, 那么Thread.sleep
不會(huì)讓線程釋放鎖.
Thread.sleep
和Object.wait
都會(huì)暫停當(dāng)前的線程. OS會(huì)將執(zhí)行時(shí)間分配給其它線程. 區(qū)別是, 調(diào)用wait
后, 需要?jiǎng)e的線程執(zhí)行notify/notifyAll
才能夠重新獲得CPU執(zhí)行時(shí)間.
上下文切換
對于單核CPU來說(對于多核CPU澎语,此處就理解為一個(gè)核),CPU在一個(gè)時(shí)刻只能運(yùn)行一個(gè)線程,當(dāng)在運(yùn)行一個(gè)線程的過程中轉(zhuǎn)去運(yùn)行另外一個(gè)線程擅羞,這個(gè)叫做線程上下文切換(對于進(jìn)程也是類似)尸变。
由于可能當(dāng)前線程的任務(wù)并沒有執(zhí)行完畢,所以在切換時(shí)需要保存線程的運(yùn)行狀態(tài)减俏,以便下次重新切換回來時(shí)能夠繼續(xù)切換之前的狀態(tài)運(yùn)行召烂。舉個(gè)簡單的例子:比如一個(gè)線程A正在讀取一個(gè)文件的內(nèi)容,正讀到文件的一半娃承,此時(shí)需要暫停線程A奏夫,轉(zhuǎn)去執(zhí)行線程B,當(dāng)再次切換回來執(zhí)行線程A的時(shí)候历筝,我們不希望線程A又從文件的開頭來讀取酗昼。
因此需要記錄線程A的運(yùn)行狀態(tài),那么會(huì)記錄哪些數(shù)據(jù)呢梳猪?因?yàn)橄麓位謴?fù)時(shí)需要知道在這之前當(dāng)前線程已經(jīng)執(zhí)行到哪條指令了麻削,所以需要記錄程序計(jì)數(shù)器的值,另外比如說線程正在進(jìn)行某個(gè)計(jì)算的時(shí)候被掛起了舔示,那么下次繼續(xù)執(zhí)行的時(shí)候需要知道之前掛起時(shí)變量的值時(shí)多少碟婆,因此需要記錄CPU寄存器的狀態(tài)电抚。所以一般來說惕稻,線程上下文切換過程中會(huì)記錄程序計(jì)數(shù)器、CPU寄存器狀態(tài)等數(shù)據(jù)蝙叛。
說簡單點(diǎn)的:對于線程的上下文切換實(shí)際上就是 存儲(chǔ)和恢復(fù)CPU狀態(tài)的過程俺祠,它使得線程執(zhí)行能夠從中斷點(diǎn)恢復(fù)執(zhí)行。
雖然多線程可以使得任務(wù)執(zhí)行的效率得到提升借帘,但是由于在線程切換時(shí)同樣會(huì)帶來一定的開銷代價(jià)蜘渣,并且多個(gè)線程會(huì)導(dǎo)致系統(tǒng)資源占用的增加,所以在進(jìn)行多線程編程時(shí)要注意這些因素肺然。
在Android平臺(tái)里蔫缸,UI線程與其子線程之分工是很明確的:
- 子線程負(fù)責(zé)執(zhí)行費(fèi)時(shí)的工作。
- 主線程負(fù)責(zé)UI的操作或事件际起;子線程不可以插手有關(guān)UI的事拾碌。
在同一進(jìn)程里的主、子線程之間可以透過MessageQueue來互相溝通街望。當(dāng)一個(gè)新進(jìn)程新誕生時(shí)校翔,會(huì)同時(shí)誕生一個(gè)主線程,也替主線程建立一個(gè)MessageQueue灾前,以及負(fù)責(zé)管理MessageQueue的Looper類別之對象防症。子線程可以透過Handler對象而將Message對象丟(Post)到主線程的MessageQueue里,而主線程的Looper對象就會(huì)接到這個(gè)Message對象,并依據(jù)其內(nèi)容而呼叫適當(dāng)?shù)暮瘮?shù)來處理蔫敲。