線程是指程序在執(zhí)行過程中谓松,能過執(zhí)行程序代碼的一個(gè)執(zhí)行單元践剂。在Java中,線程有4種狀態(tài):運(yùn)行逊脯、就緒、掛起和結(jié)束军洼。
進(jìn)程是指一段正在執(zhí)行的程序巩螃。而線程有時(shí)被稱為輕量級(jí)進(jìn)程匕争,它是程序執(zhí)行的最小單元避乏,一個(gè)進(jìn)程可以擁有多個(gè)線程,各個(gè)線程之間共享程序的內(nèi)存空間(代碼段甘桑、數(shù)據(jù)段和堆空間)及一些進(jìn)程級(jí)的資源(如打開文件)拍皮,但是各個(gè)線程擁有自己的棧空間跑杭。
多線程的使用的好處:
- 使用多線程可以減少程序的響應(yīng)時(shí)間铆帽。
- 與進(jìn)程相比,線程的創(chuàng)建和切換開銷更小德谅。由于啟動(dòng)一個(gè)新的線程必須給這個(gè)線程分配獨(dú)立的地址空間爹橱,建立許多數(shù)據(jù)結(jié)構(gòu)來維護(hù)線程代碼段、數(shù)據(jù)段等信息窄做,而運(yùn)行于同一個(gè)進(jìn)程內(nèi)的線程共享代碼段愧驱、數(shù)據(jù)段,線程的啟動(dòng)或切換的開銷比進(jìn)程少很多浸策。同時(shí)多線程在數(shù)據(jù)共享方面效率非常高
- 多CPU或多核計(jì)算機(jī)本身就具有執(zhí)行多線程的能力冯键,在這些計(jì)算機(jī)上使用多線程能提高CPU的利用率。
- 使用多線程能簡(jiǎn)化程序的結(jié)構(gòu)庸汗,使程序便于理解和維護(hù)惫确。
實(shí)現(xiàn)多線程的常用方法:
繼承Thread類,重寫run()方法;
Thread本質(zhì)上也是實(shí)現(xiàn)了Runnable接口的一個(gè)實(shí)例改化,它代表一個(gè)線程的實(shí)例掩蛤,并且,啟動(dòng)線程的唯一方法是通過Thread類的start()方法陈肛。start()方法是一個(gè)本地方法揍鸟,它將啟動(dòng)一個(gè)新線程,并執(zhí)行run()方法(Thread中提供的run()方法是一個(gè)空方法)句旱。這種方式通過自定義直接extends Thread阳藻,并重寫run()方法,就可以起到新線程并執(zhí)行自己定義的run()方法谈撒。調(diào)用start()方法后并不是執(zhí)行多線程代碼腥泥,而是使用該線程變?yōu)榭蛇\(yùn)行態(tài)(Runnable),什么時(shí)候運(yùn)行多線程代碼由操作系統(tǒng)決定啃匿。-
實(shí)現(xiàn)Runnable接口蛔外,并實(shí)現(xiàn)該接口run()方法;
- 自定義類并實(shí)現(xiàn)Runnable接口溯乒,實(shí)現(xiàn)run()方法夹厌。
- 創(chuàng)建Thread對(duì)象,用實(shí)現(xiàn)Runnable接口的對(duì)象作為實(shí)例化該Thread對(duì)象裆悄。
- 調(diào)用Thread的start()方法矛纹。
class MyThread implements Runnable{
public void run(){
System.out.println("Thread body");
}
}
public class Test{
public static void main(String []args) {
MyThread thread = new MyThread():
Thread t = new Thread(thread);
t.start();
}
}
不管是通過繼承Thread類還是通過使用Runnable接口實(shí)現(xiàn)多線程的方法崖技,最終還是通過Thread的對(duì)象的API來控制線程的钟哥。
- 實(shí)現(xiàn)Callable接口,重寫call()方法
Callable泛型接口實(shí)際是屬于Executor框架中的功能類腻贰,有一個(gè)泛型參數(shù)V,Callable接口與Runnable接口的功能類似冀瓦,但提供了比Runnable更強(qiáng)大的功能。- Callable可以在任務(wù)結(jié)束后提供一個(gè)返回值(類型為V)的call()函數(shù)翼闽,Runnable無法提供這個(gè)功能洲炊。
- Callable中的call()方法可以拋出異常尼啡,而Runnable無法提供這個(gè)功能询微。
- 運(yùn)行Callable可以拿到一個(gè)Future對(duì)象,F(xiàn)uture對(duì)象表示異步計(jì)算的結(jié)果书聚,它提供了檢查計(jì)算是否完成的方法藻雌。由于線程屬于異步計(jì)算模型,因此無法從別的線程中得到函數(shù)的返回值蹦疑,在這種情況下,就可以使用Future來監(jiān)視目標(biāo)線程調(diào)用call()方法的情況,當(dāng)調(diào)用Future的get()方法以獲取結(jié)果時(shí)腔呜,當(dāng)前線程會(huì)阻塞,直到call()方法結(jié)束返回結(jié)果膝但。
public class CallableAndFuture{
//創(chuàng)建線程類
public static class CallableTest implements Callable<String> {
public String call() throws Exception{
return "Hello world!"
}
}
public static void main(String []args) {
ExecutorService threaPool = Executor.newSingleThreadExecutor();
//啟動(dòng)線程
Future<String> future = threadPool.submit(new CallableTest());
try{
System.out.println("waiting thread to finish!");
System.out.println(future.get());//等待線程結(jié)束谤草,并獲取返回結(jié)果
}
}
}
注:
一般推薦實(shí)現(xiàn)Runnable接口的方式,原因是:
- Thread類定義了多種方法可以被派生類使用或重寫丑孩。但是只有run()方法是必須被重寫的,在run()方法中實(shí)現(xiàn)這個(gè)線程的主要功能略贮。這當(dāng)然是實(shí)現(xiàn)Runnable接口所需要的方法仗岖。
- 很多人認(rèn)為一個(gè)類僅在他們需要被加強(qiáng)或修改時(shí)才會(huì)被繼承,因此轧拄,如果沒有畢業(yè)重寫Thread類中的其他方法,那么通過繼承Thread的實(shí)現(xiàn)方式與實(shí)現(xiàn)Runnable接口的效果相同拄丰,在這種情況下最好通過實(shí)現(xiàn)Runnable接口的方式來創(chuàng)建線程。
run()方法與start()方法有什么區(qū)別
系統(tǒng)通過調(diào)用線程類的start()方法來啟動(dòng)一個(gè)線程愈案,此時(shí)該線程處于就緒狀態(tài),而非運(yùn)行狀態(tài)站绪,也就意味著這個(gè)線程可以被JVM來調(diào)度執(zhí)行,在調(diào)度過程中恢准,JVM通過調(diào)用線程類的run()方法來完成實(shí)際的操作做,當(dāng)run()方法結(jié)束后涂召,此線程就會(huì)終止敏沉。
多線程同步的實(shí)現(xiàn)方法
- synchronized關(guān)鍵字
在Java語言中,每個(gè)對(duì)象都有也給對(duì)象鎖與之關(guān)聯(lián)盟迟,該鎖表明對(duì)象在任何時(shí)候只允許被一個(gè)線程所擁有,當(dāng)一個(gè)線程調(diào)用對(duì)象的一段synchronized代碼時(shí)迫皱,需要先獲取這個(gè)鎖辖众,然后去執(zhí)行相應(yīng)的代碼,執(zhí)行結(jié)束后凹炸,釋放鎖。
synchronized關(guān)鍵字主要有兩種用法(synchronized方法和synchronized塊)饲握,還可以用于靜態(tài)方法蚕键、類或某個(gè)實(shí)例,但對(duì)程序的效率影響很大锣光。- synchronized方法。在方法的聲明前加入synchronized關(guān)鍵字蹬刷。只要把多個(gè)線程對(duì)類需要被同步的資源的操作放到synchronized方法中,就能保證這個(gè)方法在同一時(shí)刻只能被一個(gè)線程訪問办成,從而保證了多線程訪問的安全性。然而迂卢,當(dāng)一個(gè)方法的方法體規(guī)模非常大時(shí),把該方法聲明為synchronized會(huì)大大影響程序的執(zhí)行效率靶壮。
- synchronized塊。synchronized塊既可以把任意的代碼段聲明為synchronized腾降,也可以指定上鎖的對(duì)象碎绎,有非常高的靈活性。
synchronized(syncObject){
//訪問syncObject的代碼
}
- wait()方法和notify()方法
在synchronized代碼被執(zhí)行期間映穗,線程可以調(diào)用對(duì)象的wait()方法,釋放對(duì)象鎖,進(jìn)入一個(gè)和該對(duì)象相關(guān)的等待池中宿接,并且可以調(diào)用notify()方法或notifyAll()方法通知正在等待的其他線程可以訪問。notify()方法僅喚醒一個(gè)線程(等待對(duì)列中的第一個(gè)線程)并允許它去獲得鎖梢卸,notifyAll()方法喚醒所有等待這個(gè)對(duì)象的線程并允許它們?nèi)カ@得鎖(所有線程根據(jù)優(yōu)先級(jí)獲得鎖)副女。必須放在synchronized塊中。 - Lock
- lock()以阻塞的方式獲取鎖碑幅,如果獲得鎖,立即返回恤批;如果別的線程持有鎖,當(dāng)前線程等待喜庞,直到獲得鎖后返回。
- tryLock()以非阻塞的方式獲取鎖延都,只是嘗試性地獲取一下鎖,如果獲取到鎖晰房,立即返回true;否則月帝,返回false幽污。
- tryLock(long timeout, TimeUnit unit)如果獲取了鎖,返回true距误。否則等待參數(shù)定的時(shí)間單元,在等待的過程中趁俊,如果獲取鎖,返回true寺擂;如果超時(shí)泼掠,返回false。
- lockInterruptibly()如果獲取鎖择镇,立即返回true,如果沒有家坎,當(dāng)前線程處于休眠狀態(tài)吝梅,直到獲得鎖。
線程的wait()憔涉、sleep()、join()穿扳、yeild()
- wait()上述
- sleep()是Tread類的靜態(tài)方法衩侥,作用是使調(diào)用線程進(jìn)入睡眠狀態(tài)茫死。因?yàn)槭荰hread類的靜態(tài)方法履羞,因此它不能改變對(duì)象的機(jī)鎖。所以忆首,當(dāng)一個(gè)synchronized塊中調(diào)用sleep()方法時(shí),線程雖然休眠了详幽,但是對(duì)象的機(jī)鎖并沒有被釋放,其他線程無法訪問這個(gè)對(duì)象唇聘。sleep()方法必須捕抓異常柱搜,
- join()等待目標(biāo)線程執(zhí)行完成之后 再繼續(xù)執(zhí)行
- yeild()線程禮讓。目標(biāo)線程有運(yùn)行狀態(tài)轉(zhuǎn)換為就緒狀態(tài)也就是讓出執(zhí)行權(quán)限宪肖,讓跟其他線程得以優(yōu)先執(zhí)行,只會(huì)給相同優(yōu)先級(jí)或更高優(yōu)先級(jí)的線程以運(yùn)行機(jī)會(huì)匈庭。