Java多线程干货系列(1):Java多线程基础
轉(zhuǎn)載自??Java多線程干貨系列(1):Java多線程基礎(chǔ)
前言
多線程并發(fā)編程是Java編程中重要的一塊內(nèi)容,也是面試重點(diǎn)覆蓋區(qū)域,所以學(xué)好多線程并發(fā)編程對(duì)我們來(lái)說(shuō)極其重要,下面跟我一起開(kāi)啟本次的學(xué)習(xí)之旅吧。
正文
線程與進(jìn)程
1 線程:進(jìn)程中負(fù)責(zé)程序執(zhí)行的執(zhí)行單元
線程本身依靠程序進(jìn)行運(yùn)行
線程是程序中的順序控制流,只能使用分配給程序的資源和環(huán)境
2 進(jìn)程:執(zhí)行中的程序
一個(gè)進(jìn)程至少包含一個(gè)線程
3 單線程:程序中只存在一個(gè)線程,實(shí)際上主方法就是一個(gè)主線程
4 多線程:在一個(gè)程序中運(yùn)行多個(gè)任務(wù)
目的是更好地使用CPU資源
線程的實(shí)現(xiàn)
繼承Thread類
在java.lang包中定義, 繼承Thread類必須重寫(xiě)run()方法
class MyThread extends Thread{private static int num = 0;public MyThread(){num++;}@Overridepublic void run() {System.out.println("主動(dòng)創(chuàng)建的第"+num+"個(gè)線程");} }創(chuàng)建好了自己的線程類之后,就可以創(chuàng)建線程對(duì)象了,然后通過(guò)start()方法去啟動(dòng)線程。注意,不是調(diào)用run()方法啟動(dòng)線程,run方法中只是定義需要執(zhí)行的任務(wù),如果調(diào)用run方法,即相當(dāng)于在主線程中執(zhí)行run方法,跟普通的方法調(diào)用沒(méi)有任何區(qū)別,此時(shí)并不會(huì)創(chuàng)建一個(gè)新的線程來(lái)執(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++;}@Overridepublic void run() {System.out.println("主動(dòng)創(chuàng)建的第"+num+"個(gè)線程");} }在上面代碼中,通過(guò)調(diào)用start()方法,就會(huì)創(chuàng)建一個(gè)新的線程了。為了分清start()方法調(diào)用和run()方法調(diào)用的區(qū)別,請(qǐng)看下面一個(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;}@Overridepublic void run() {System.out.println("name:"+name+" 子線程ID:"+Thread.currentThread().getId());} }運(yùn)行結(jié)果:
從輸出結(jié)果可以得出以下結(jié)論:
1)thread1和thread2的線程ID不同,thread2和主線程ID相同,說(shuō)明通過(guò)run方法調(diào)用并不會(huì)創(chuàng)建新的線程,而是在主線程中直接運(yùn)行run方法,跟普通的方法調(diào)用沒(méi)有任何區(qū)別;
2)雖然thread1的start方法調(diào)用在thread2的run方法前面調(diào)用,但是先輸出的是thread2的run方法調(diào)用的相關(guān)信息,說(shuō)明新線程創(chuàng)建的過(guò)程不會(huì)阻塞主線程的后續(xù)執(zhí)行。
實(shí)現(xiàn)Runnable接口
在Java中創(chuàng)建線程除了繼承Thread類之外,還可以通過(guò)實(shí)現(xiàn)Runnable接口來(lái)實(shí)現(xiàn)類似的功能。實(shí)現(xiàn)Runnable接口必須重寫(xiě)其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() {}@Overridepublic void run() {System.out.println("子線程ID:"+Thread.currentThread().getId());} }Runnable的中文意思是“任務(wù)”,顧名思義,通過(guò)實(shí)現(xiàn)Runnable接口,我們定義了一個(gè)子任務(wù),然后將子任務(wù)交由Thread去執(zhí)行。注意,這種方式必須將Runnable作為T(mén)hread類的參數(shù),然后通過(guò)Thread的start方法來(lái)創(chuàng)建一個(gè)新線程來(lái)執(zhí)行該子任務(wù)。如果調(diào)用Runnable的run方法的話,是不會(huì)創(chuàng)建新線程的,這根普通的方法調(diào)用沒(méi)有任何區(qū)別。
事實(shí)上,查看Thread類的實(shí)現(xiàn)源代碼會(huì)發(fā)現(xiàn)Thread類是實(shí)現(xiàn)了Runnable接口的。
在Java中,這2種方式都可以用來(lái)創(chuàng)建線程去執(zhí)行子任務(wù),具體選擇哪一種方式要看自己的需求。直接繼承Thread類的話,可能比實(shí)現(xiàn)Runnable接口看起來(lái)更加簡(jiǎn)潔,但是由于Java只允許單繼承,所以如果自定義類需要繼承其他類,則只能選擇實(shí)現(xiàn)Runnable接口。
使用ExecutorService、Callable、Future實(shí)現(xiàn)有返回結(jié)果的多線程
多線程后續(xù)會(huì)學(xué)到,這里暫時(shí)先知道一下有這種方法即可。
ExecutorService、Callable、Future這個(gè)對(duì)象實(shí)際上都是屬于Executor框架中的功能類。想要詳細(xì)了解Executor框架的可以訪問(wèn)http://www.javaeye.com/topic/366591?,這里面對(duì)該框架做了很詳細(xì)的解釋。返回結(jié)果的線程是在JDK1.5中引入的新特征,確實(shí)很實(shí)用,有了這種特征我就不需要再為了得到返回值而大費(fèi)周折了,而且即便實(shí)現(xiàn)了也可能漏洞百出。
可返回值的任務(wù)必須實(shí)現(xiàn)Callable接口,類似的,無(wú)返回值的任務(wù)必須Runnable接口。執(zhí)行Callable任務(wù)后,可以獲取一個(gè)Future的對(duì)象,在該對(duì)象上調(diào)用get就可以獲取到Callable任務(wù)返回的Object了,再結(jié)合線程池接口ExecutorService就可以實(shí)現(xiàn)傳說(shuō)中有返回結(jié)果的多線程了。下面提供了一個(gè)完整的有返回結(jié)果的多線程測(cè)試?yán)?#xff0c;在JDK1.5下驗(yàn)證過(guò)沒(méi)問(wèn)題可以直接使用。代碼如下:
/** * 有返回值的線程 */ @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ì)算完成。
線程的狀態(tài)
在正式學(xué)習(xí)Thread類中的具體方法之前,我們先來(lái)了解一下線程有哪些狀態(tài),這個(gè)將會(huì)有助于后面對(duì)Thread類中的方法的理解。
- 創(chuàng)建(new)狀態(tài): 準(zhǔn)備好了一個(gè)多線程的對(duì)象
- 就緒(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è)線程來(lái)執(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)過(guò)程中,可能有多個(gè)原因?qū)е庐?dāng)前線程不繼續(xù)運(yùn)行下去,比如用戶主動(dòng)讓線程睡眠(睡眠一定的時(shí)間之后再重新執(zhí)行)、用戶主動(dòng)讓線程等待,或者被同步塊給阻塞,此時(shí)就對(duì)應(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è)也是可以的,只不過(guò)這里我想將線程的狀態(tài)和Java中的方法調(diào)用聯(lián)系起來(lái),所以將waiting和time waiting兩個(gè)狀態(tài)分離出來(lá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í)間.
上下文切換
對(duì)于單核CPU來(lái)說(shuō)(對(duì)于多核CPU,此處就理解為一個(gè)核),CPU在一個(gè)時(shí)刻只能運(yùn)行一個(gè)線程,當(dāng)在運(yùn)行一個(gè)線程的過(guò)程中轉(zhuǎn)去運(yùn)行另外一個(gè)線程,這個(gè)叫做線程上下文切換(對(duì)于進(jìn)程也是類似)。
由于可能當(dāng)前線程的任務(wù)并沒(méi)有執(zhí)行完畢,所以在切換時(shí)需要保存線程的運(yùn)行狀態(tài),以便下次重新切換回來(lái)時(shí)能夠繼續(xù)切換之前的狀態(tài)運(yùn)行。舉個(gè)簡(jiǎn)單的例子:比如一個(gè)線程A正在讀取一個(gè)文件的內(nèi)容,正讀到文件的一半,此時(shí)需要暫停線程A,轉(zhuǎn)去執(zhí)行線程B,當(dāng)再次切換回來(lái)執(zhí)行線程A的時(shí)候,我們不希望線程A又從文件的開(kāi)頭來(lái)讀取。
因此需要記錄線程A的運(yùn)行狀態(tài),那么會(huì)記錄哪些數(shù)據(jù)呢?因?yàn)橄麓位謴?fù)時(shí)需要知道在這之前當(dāng)前線程已經(jīng)執(zhí)行到哪條指令了,所以需要記錄程序計(jì)數(shù)器的值,另外比如說(shuō)線程正在進(jìn)行某個(gè)計(jì)算的時(shí)候被掛起了,那么下次繼續(xù)執(zhí)行的時(shí)候需要知道之前掛起時(shí)變量的值時(shí)多少,因此需要記錄CPU寄存器的狀態(tài)。所以一般來(lái)說(shuō),線程上下文切換過(guò)程中會(huì)記錄程序計(jì)數(shù)器、CPU寄存器狀態(tài)等數(shù)據(jù)。
說(shuō)簡(jiǎn)單點(diǎn)的:對(duì)于線程的上下文切換實(shí)際上就是?存儲(chǔ)和恢復(fù)CPU狀態(tài)的過(guò)程,它使得線程執(zhí)行能夠從中斷點(diǎn)恢復(fù)執(zhí)行。
雖然多線程可以使得任務(wù)執(zhí)行的效率得到提升,但是由于在線程切換時(shí)同樣會(huì)帶來(lái)一定的開(kāi)銷代價(jià),并且多個(gè)線程會(huì)導(dǎo)致系統(tǒng)資源占用的增加,所以在進(jìn)行多線程編程時(shí)要注意這些因素。
線程的常用方法
| 1 | public void start() | 使該線程開(kāi)始執(zhí)行;Java 虛擬機(jī)調(diào)用該線程的 run 方法。 |
| 2 | public void run() | 如果該線程是使用獨(dú)立的 Runnable 運(yùn)行對(duì)象構(gòu)造的,則調(diào)用該 Runnable 對(duì)象的 run 方法;否則,該方法不執(zhí)行任何操作并返回。 |
| 3 | public final void setName(String name) | 改變線程名稱,使之與參數(shù) name 相同。 |
| 4 | public final void setPriority(int priority) | 更改線程的優(yōu)先級(jí)。 |
| 5 | public final void setDaemon(boolean on) | 將該線程標(biāo)記為守護(hù)線程或用戶線程。 |
| 6 | public final void join(long millisec) | 等待該線程終止的時(shí)間最長(zhǎng)為 millis 毫秒。 |
| 7 | public void interrupt() | 中斷線程。 |
| 8 | public final boolean isAlive() | 測(cè)試線程是否處于活動(dòng)狀態(tài)。 |
| 9 | public static void yield() | 暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。 |
| 10 | public static void sleep(long millisec) | 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計(jì)時(shí)器和調(diào)度程序精度和準(zhǔn)確性的影響。 |
| 11 | public static Thread currentThread() | 返回對(duì)當(dāng)前正在執(zhí)行的線程對(duì)象的引用。 |
靜態(tài)方法
currentThread()方法
currentThread()方法可以返回代碼段正在被哪個(gè)線程調(diào)用的信息。
public class Run1{public static void main(String[] args){ System.out.println(Thread.currentThread().getName());} }sleep()方法
方法sleep()的作用是在指定的毫秒數(shù)內(nèi)讓當(dāng)前“正在執(zhí)行的線程”休眠(暫停執(zhí)行)。這個(gè)“正在執(zhí)行的線程”是指this.currentThread()返回的線程。
sleep方法有兩個(gè)重載版本:
sleep(long millis) //參數(shù)為毫秒 sleep(long millis,int nanoseconds) //第一參數(shù)為毫秒,第二個(gè)參數(shù)為納秒sleep相當(dāng)于讓線程睡眠,交出CPU,讓CPU去執(zhí)行其他的任務(wù)。
但是有一點(diǎn)要非常注意,sleep方法不會(huì)釋放鎖,也就是說(shuō)如果當(dāng)前線程持有對(duì)某個(gè)對(duì)象的鎖,則即使調(diào)用sleep方法,其他線程也無(wú)法訪問(wèn)這個(gè)對(duì)象。看下面這個(gè)例子就清楚了:
public class Test {private int i = 10;private Object object = new Object();public static void main(String[] args) throws IOException {Test test = new Test();MyThread thread1 = test.new MyThread();MyThread thread2 = test.new MyThread();thread1.start();thread2.start();}class MyThread extends Thread{@Overridepublic void run() {synchronized (object) {i++;System.out.println("i:"+i);try {System.out.println("線程"+Thread.currentThread().getName()+"進(jìn)入睡眠狀態(tài)");Thread.currentThread().sleep(10000);} catch (InterruptedException e) {// TODO: handle exception}System.out.println("線程"+Thread.currentThread().getName()+"睡眠結(jié)束");i++;System.out.println("i:"+i);}}} }輸出結(jié)果:
從上面輸出結(jié)果可以看出,當(dāng)Thread-0進(jìn)入睡眠狀態(tài)之后,Thread-1并沒(méi)有去執(zhí)行具體的任務(wù)。只有當(dāng)Thread-0執(zhí)行完之后,此時(shí)Thread-0釋放了對(duì)象鎖,Thread-1才開(kāi)始執(zhí)行。
注意,如果調(diào)用了sleep方法,必須捕獲InterruptedException異常或者將該異常向上層拋出。當(dāng)線程睡眠時(shí)間滿后,不一定會(huì)立即得到執(zhí)行,因?yàn)榇藭r(shí)可能CPU正在執(zhí)行其他的任務(wù)。所以說(shuō)調(diào)用sleep方法相當(dāng)于讓線程進(jìn)入阻塞狀態(tài)。
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ì)。
注意,調(diào)用yield方法并不會(huì)讓線程進(jìn)入阻塞狀態(tài),而是讓線程重回就緒狀態(tài),它只需要等待重新獲取CPU執(zhí)行時(shí)間,這一點(diǎn)是和sleep方法不一樣的。
代碼:
public class MyThread extends Thread{@Overridepublic void run() {long beginTime=System.currentTimeMillis();int count=0;for (int i=0;i<50000000;i++){count=count+(i+1);//Thread.yield();}long endTime=System.currentTimeMillis();System.out.println("用時(shí):"+(endTime-beginTime)+" 毫秒!");} }public class Run {public static void main(String[] args) {MyThread t= new MyThread();t.start();} }執(zhí)行結(jié)果:
用時(shí):3 毫秒!
用時(shí):16080 毫秒!
對(duì)象方法
start()方法
start()用來(lái)啟動(dòng)一個(gè)線程,當(dāng)調(diào)用start方法后,系統(tǒng)才會(huì)開(kāi)啟一個(gè)新的線程來(lái)執(zhí)行用戶定義的子任務(wù),在這個(gè)過(guò)程中,會(huì)為相應(yīng)的線程分配需要的資源。
run()方法
run()方法是不需要用戶來(lái)調(diào)用的,當(dāng)通過(guò)start方法啟動(dòng)一個(gè)線程之后,當(dāng)線程獲得了CPU執(zhí)行時(shí)間,便進(jìn)入run方法體去執(zhí)行具體的任務(wù)。注意,繼承Thread類必須重寫(xiě)run方法,在run方法中定義具體要執(zhí)行的任務(wù)。
getId()
getId()的作用是取得線程的唯一標(biāo)識(shí)
代碼:
public class Test {public static void main(String[] args) {Thread t= Thread.currentThread();System.out.println(t.getName()+" "+t.getId());} }輸出:
main 1
isAlive()方法
方法isAlive()的功能是判斷當(dāng)前線程是否處于活動(dòng)狀態(tài)
代碼:
public class MyThread extends Thread{@Overridepublic void run() {System.out.println("run="+this.isAlive());} } public class RunTest {public static void main(String[] args) throws InterruptedException {MyThread myThread=new MyThread();System.out.println("begin =="+myThread.isAlive());myThread.start();System.out.println("end =="+myThread.isAlive());} }程序運(yùn)行結(jié)果:
begin ==false
run=true end ==false方法isAlive()的作用是測(cè)試線程是否偶處于活動(dòng)狀態(tài)。什么是活動(dòng)狀態(tài)呢?活動(dòng)狀態(tài)就是線程已經(jīng)啟動(dòng)且尚未終止。線程處于正在運(yùn)行或準(zhǔn)備開(kāi)始運(yùn)行的狀態(tài),就認(rèn)為線程是“存活”的。
有個(gè)需要注意的地方
System.out.println("end =="+myThread.isAlive());雖然上面的實(shí)例中打印的值是true,但此值是不確定的。打印true值是因?yàn)閙yThread線程還未執(zhí)行完畢,所以輸出true。如果代碼改成下面這樣,加了個(gè)sleep休眠:
public static void main(String[] args) throws InterruptedException {MyThread myThread=new MyThread();System.out.println("begin =="+myThread.isAlive());myThread.start();Thread.sleep(1000);System.out.println("end =="+myThread.isAlive());}則上述代碼運(yùn)行的結(jié)果輸出為false,因?yàn)閙ythread對(duì)象已經(jīng)在1秒之內(nèi)執(zhí)行完畢。
join()方法
在很多情況下,主線程創(chuàng)建并啟動(dòng)了線程,如果子線程中藥進(jìn)行大量耗時(shí)運(yùn)算,主線程往往將早于子線程結(jié)束之前結(jié)束。這時(shí),如果主線程想等待子線程執(zhí)行完成之后再結(jié)束,比如子線程處理一個(gè)數(shù)據(jù),主線程要取得這個(gè)數(shù)據(jù)中的值,就要用到j(luò)oin()方法了。方法join()的作用是等待線程對(duì)象銷毀。
public class Thread4 extends Thread{public Thread4(String name) {super(name);}public void run() {for (int i = 0; i < 5; i++) {System.out.println(getName() + " " + i);}}public static void main(String[] args) throws InterruptedException {// 啟動(dòng)子進(jìn)程new Thread4("new thread").start();for (int i = 0; i < 10; i++) {if (i == 5) {Thread4 th = new Thread4("joined thread");th.start();th.join();}System.out.println(Thread.currentThread().getName() + " " + i);}} }執(zhí)行結(jié)果:
main??0 main??1 main??2 main??3 main??4 new thread??0 new thread??1 new thread??2 new thread??3 new thread??4 joined thread??0 joined thread??1 joined thread??2 joined thread??3 joined thread??4 main??5 main??6 main??7 main??8 main??9由上可以看出main主線程等待joined thread線程先執(zhí)行完了才結(jié)束的。如果把th.join()這行注釋掉,運(yùn)行結(jié)果如下:
main??0 main??1 main??2 main??3 main??4 main??5 main??6 main??7 main??8 main??9 new thread??0 new thread??1 new thread??2 new thread??3 new thread??4 joined thread??0 joined thread??1 joined thread??2 joined thread??3 joined thread??4getName和setName
用來(lái)得到或者設(shè)置線程名稱。
getPriority和setPriority
用來(lái)獲取和設(shè)置線程優(yōu)先級(jí)。
setDaemon和isDaemon
用來(lái)設(shè)置線程是否成為守護(hù)線程和判斷線程是否是守護(hù)線程。
守護(hù)線程和用戶線程的區(qū)別在于:守護(hù)線程依賴于創(chuàng)建它的線程,而用戶線程則不依賴。舉個(gè)簡(jiǎn)單的例子:如果在main線程中創(chuàng)建了一個(gè)守護(hù)線程,當(dāng)main方法運(yùn)行完畢之后,守護(hù)線程也會(huì)隨著消亡。而用戶線程則不會(huì),用戶線程會(huì)一直運(yùn)行直到其運(yùn)行完畢。在JVM中,像垃圾收集器線程就是守護(hù)線程。
在上面已經(jīng)說(shuō)到了Thread類中的大部分方法,那么Thread類中的方法調(diào)用到底會(huì)引起線程狀態(tài)發(fā)生怎樣的變化呢?下面一幅圖就是在上面的圖上進(jìn)行改進(jìn)而來(lái)的:
| 用時(shí):3 毫秒! |
停止線程
停止線程是在多線程開(kāi)發(fā)時(shí)很重要的技術(shù)點(diǎn),掌握此技術(shù)可以對(duì)線程的停止進(jìn)行有效的處理。
停止一個(gè)線程可以使用Thread.stop()方法,但最好不用它。該方法是不安全的,已被棄用。
在Java中有以下3種方法可以終止正在運(yùn)行的線程:
- 使用退出標(biāo)志,使線程正常退出,也就是當(dāng)run方法完成后線程終止
- 使用stop方法強(qiáng)行終止線程,但是不推薦使用這個(gè)方法,因?yàn)閟top和suspend及resume一樣,都是作廢過(guò)期的方法,使用他們可能產(chǎn)生不可預(yù)料的結(jié)果。
- 使用interrupt方法中斷線程,但這個(gè)不會(huì)終止一個(gè)正在運(yùn)行的線程,還需要加入一個(gè)判斷才可以完成線程的停止。
暫停線程
interrupt()方法
線程的優(yōu)先級(jí)
在操作系統(tǒng)中,線程可以劃分優(yōu)先級(jí),優(yōu)先級(jí)較高的線程得到的CPU資源較多,也就是CPU優(yōu)先執(zhí)行優(yōu)先級(jí)較高的線程對(duì)象中的任務(wù)。
設(shè)置線程優(yōu)先級(jí)有助于幫“線程規(guī)劃器”確定在下一次選擇哪一個(gè)線程來(lái)優(yōu)先執(zhí)行。
設(shè)置線程的優(yōu)先級(jí)使用setPriority()方法,此方法在JDK的源碼如下:
public final void setPriority(int newPriority) {ThreadGroup g;checkAccess();if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {throw new IllegalArgumentException();}if((g = getThreadGroup()) != null) {if (newPriority > g.getMaxPriority()) {newPriority = g.getMaxPriority();}setPriority0(priority = newPriority);}}在Java中,線程的優(yōu)先級(jí)分為1~10這10個(gè)等級(jí),如果小于1或大于10,則JDK拋出異常throw new IllegalArgumentException()。
JDK中使用3個(gè)常量來(lái)預(yù)置定義優(yōu)先級(jí)的值,代碼如下:
public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10;線程優(yōu)先級(jí)特性:
- 繼承性
比如A線程啟動(dòng)B線程,則B線程的優(yōu)先級(jí)與A是一樣的。 - 規(guī)則性
高優(yōu)先級(jí)的線程總是大部分先執(zhí)行完,但不代表高優(yōu)先級(jí)線程全部先執(zhí)行完。 - 隨機(jī)性
優(yōu)先級(jí)較高的線程不一定每一次都先執(zhí)行完。
守護(hù)線程
在Java線程中有兩種線程,一種是User Thread(用戶線程),另一種是Daemon Thread(守護(hù)線程)。
Daemon的作用是為其他線程的運(yùn)行提供服務(wù),比如說(shuō)GC線程。其實(shí)User Thread線程和Daemon Thread守護(hù)線程本質(zhì)上來(lái)說(shuō)去沒(méi)啥區(qū)別的,唯一的區(qū)別之處就在虛擬機(jī)的離開(kāi):如果User Thread全部撤離,那么Daemon Thread也就沒(méi)啥線程好服務(wù)的了,所以虛擬機(jī)也就退出了。
守護(hù)線程并非虛擬機(jī)內(nèi)部可以提供,用戶也可以自行的設(shè)定守護(hù)線程,方法:public final void setDaemon(boolean on) ;但是有幾點(diǎn)需要注意:
- thread.setDaemon(true)必須在thread.start()之前設(shè)置,否則會(huì)跑出一個(gè)IllegalThreadStateException異常。你不能把正在運(yùn)行的常規(guī)線程設(shè)置為守護(hù)線程。 (備注:這點(diǎn)與守護(hù)進(jìn)程有著明顯的區(qū)別,守護(hù)進(jìn)程是創(chuàng)建后,讓進(jìn)程擺脫原會(huì)話的控制+讓進(jìn)程擺脫原進(jìn)程組的控制+讓進(jìn)程擺脫原控制終端的控制;所以說(shuō)寄托于虛擬機(jī)的語(yǔ)言機(jī)制跟系統(tǒng)級(jí)語(yǔ)言有著本質(zhì)上面的區(qū)別)
- 在Daemon線程中產(chǎn)生的新線程也是Daemon的。 (這一點(diǎn)又是有著本質(zhì)的區(qū)別了:守護(hù)進(jìn)程fork()出來(lái)的子進(jìn)程不再是守護(hù)進(jìn)程,盡管它把父進(jìn)程的進(jìn)程相關(guān)信息復(fù)制過(guò)去了,但是子進(jìn)程的進(jìn)程的父進(jìn)程不是init進(jìn)程,所謂的守護(hù)進(jìn)程本質(zhì)上說(shuō)就是“父進(jìn)程掛掉,init收養(yǎng),然后文件0,1,2都是/dev/null,當(dāng)前目錄到/”)
- 不是所有的應(yīng)用都可以分配給Daemon線程來(lái)進(jìn)行服務(wù),比如讀寫(xiě)操作或者計(jì)算邏輯。因?yàn)樵贒aemon Thread還沒(méi)來(lái)的及進(jìn)行操作時(shí),虛擬機(jī)可能已經(jīng)退出了。
同步與死鎖
在代碼塊上加上”synchronized”關(guān)鍵字,則此代碼塊就稱為同步代碼塊
除了代碼塊可以同步,方法也是可以同步的
synchronized后續(xù)會(huì)單獨(dú)來(lái)學(xué)習(xí)。(●’?’●)
面試題
線程和進(jìn)程有什么區(qū)別?
答:一個(gè)進(jìn)程是一個(gè)獨(dú)立(self contained)的運(yùn)行環(huán)境,它可以被看作一個(gè)程序或者一個(gè)應(yīng)用。而線程是在進(jìn)程中執(zhí)行的一個(gè)任務(wù)。線程是進(jìn)程的子集,一個(gè)進(jìn)程可以有很多線程,每條線程并行執(zhí)行不同的任務(wù)。不同的進(jìn)程使用不同的內(nèi)存空間,而所有的線程共享一片相同的內(nèi)存空間。別把它和棧內(nèi)存搞混,每個(gè)線程都擁有單獨(dú)的棧內(nèi)存用來(lái)存儲(chǔ)本地?cái)?shù)據(jù)。
如何在Java中實(shí)現(xiàn)線程?
答:
創(chuàng)建線程有兩種方式:
一、繼承 Thread 類,擴(kuò)展線程。
二、實(shí)現(xiàn) Runnable 接口。
啟動(dòng)一個(gè)線程是調(diào)用run()還是start()方法?
答:啟動(dòng)一個(gè)線程是調(diào)用start()方法,使線程所代表的虛擬處理機(jī)處于可運(yùn)行狀態(tài),這意味著它可以由JVM 調(diào)度并執(zhí)行,這并不意味著線程就會(huì)立即運(yùn)行。run()方法是線程啟動(dòng)后要進(jìn)行回調(diào)(callback)的方法。
Thread類的sleep()方法和對(duì)象的wait()方法都可以讓線程暫停執(zhí)行,它們有什么區(qū)別?
答:sleep()方法(休眠)是線程類(Thread)的靜態(tài)方法,調(diào)用此方法會(huì)讓當(dāng)前線程暫停執(zhí)行指定的時(shí)間,將執(zhí)行機(jī)會(huì)(CPU)讓給其他線程,但是對(duì)象的鎖依然保持,因此休眠時(shí)間結(jié)束后會(huì)自動(dòng)恢復(fù)(線程回到就緒狀態(tài),請(qǐng)參考第66題中的線程狀態(tài)轉(zhuǎn)換圖)。wait()是Object類的方法,調(diào)用對(duì)象的wait()方法導(dǎo)致當(dāng)前線程放棄對(duì)象的鎖(線程暫停執(zhí)行),進(jìn)入對(duì)象的等待池(wait pool),只有調(diào)用對(duì)象的notify()方法(或notifyAll()方法)時(shí)才能喚醒等待池中的線程進(jìn)入等鎖池(lock pool),如果線程重新獲得對(duì)象的鎖就可以進(jìn)入就緒狀態(tài)。
線程的sleep()方法和yield()方法有什么區(qū)別?
答:
① sleep()方法給其他線程運(yùn)行機(jī)會(huì)時(shí)不考慮線程的優(yōu)先級(jí),因此會(huì)給低優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);yield()方法只會(huì)給相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線程以運(yùn)行的機(jī)會(huì);
② 線程執(zhí)行sleep()方法后轉(zhuǎn)入阻塞(blocked)狀態(tài),而執(zhí)行yield()方法后轉(zhuǎn)入就緒(ready)狀態(tài);
③ sleep()方法聲明拋出InterruptedException,而yield()方法沒(méi)有聲明任何異常;
④ sleep()方法比yield()方法(跟操作系統(tǒng)CPU調(diào)度相關(guān))具有更好的可移植性。
請(qǐng)說(shuō)出與線程同步以及線程調(diào)度相關(guān)的方法。
答:
- wait():使一個(gè)線程處于等待(阻塞)狀態(tài),并且釋放所持有的對(duì)象的鎖;
- sleep():使一個(gè)正在運(yùn)行的線程處于睡眠狀態(tài),是一個(gè)靜態(tài)方法,調(diào)用此方法要處理InterruptedException異常;
- notify():喚醒一個(gè)處于等待狀態(tài)的線程,當(dāng)然在調(diào)用此方法的時(shí)候,并不能確切的喚醒某一個(gè)等待狀態(tài)的線程,而是由JVM確定喚醒哪個(gè)線程,而且與優(yōu)先級(jí)無(wú)關(guān);
- notityAll():喚醒所有處于等待狀態(tài)的線程,該方法并不是將對(duì)象的鎖給所有線程,而是讓它們競(jìng)爭(zhēng),只有獲得鎖的線程才能進(jìn)入就緒狀態(tài);
總結(jié)
以上就是多線程的一些基礎(chǔ)概念,可能總結(jié)的不夠仔細(xì),多多包涵。后續(xù)會(huì)針對(duì)一些比較重要的知識(shí)點(diǎn)單獨(dú)列出來(lái)總結(jié)。學(xué)好多線程是拿高薪的基礎(chǔ),小伙伴一起加油吧!
參考
該文為本人學(xué)習(xí)的筆記,方便以后自己跳槽前復(fù)習(xí)。參考網(wǎng)上各大帖子,取其精華整合自己的理解而成。還有,關(guān)注我個(gè)人主頁(yè)的公眾號(hào),里面電子書(shū)資源有《Java多線程編程核心技術(shù)》以及《JAVA并發(fā)編程實(shí)踐》高清版,需要的小伙伴自己取。
《Java多線程編程核心技術(shù)》
《JAVA并發(fā)編程實(shí)踐》
Java并發(fā)編程:Thread類的使用
關(guān)于Java并發(fā)編程的總結(jié)和思考
JAVA多線程實(shí)現(xiàn)的三種方式
整理的思維導(dǎo)圖
個(gè)人整理的多線程基礎(chǔ)的思維導(dǎo)圖,導(dǎo)出的圖片無(wú)法查看備注的一些信息,所以需要源文件的童鞋可以關(guān)注我個(gè)人主頁(yè)上的公眾號(hào),回復(fù)多線程基礎(chǔ)即可獲取源文件。
一直覺(jué)得自己寫(xiě)的不是技術(shù),而是情懷,一篇篇文章是自己這一路走來(lái)的痕跡。靠專業(yè)技能的成功是最具可復(fù)制性的,希望我的這條路能讓你少走彎路,希望我能幫你抹去知識(shí)的蒙塵,希望我能幫你理清知識(shí)的脈絡(luò),希望未來(lái)技術(shù)之巔上有你也有我。
| 用時(shí):3 毫秒! |
總結(jié)
以上是生活随笔為你收集整理的Java多线程干货系列(1):Java多线程基础的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 巯基怎么读 巯基读音和解释
- 下一篇: 中道崩殂怎么读 中道崩殂读音和解释