一、線程的實(shí)現(xiàn)
繼承thread類重寫run()方法和實(shí)現(xiàn)Runnable接口實(shí)現(xiàn)run()方法
注意點(diǎn):1、new一個(gè)線程實(shí)例時(shí)建議都要加個(gè)線程名方便監(jiān)控和排查問題;
如new?Thread("thread?name")或thread.setName("thread?name");
2、要處理線程的中斷異常(InterruptedException);
如if?(Thread.interrupted())?{
//do?someting
};
或
Try{}
catch?(InterruptedException?e)?{
//do?someting
}
二、ThreadLocal
ThreadLocal源碼詳見文檔ThreadLocal.java;
顧名思義它是local?variable(線程局部變量)。它的功用非常簡單,就是為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,是每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)和其它線程的副本沖突。從線程的角度看,就好像每一個(gè)線程都完全擁有該變量。
注意:使用ThreadLocal,一般都是聲明在靜態(tài)變量中,如果不斷的創(chuàng)建ThreadLocal而且沒有調(diào)用其remove方法,將會(huì)導(dǎo)致內(nèi)存泄露。如果是static的ThreadLocal,一般不需要調(diào)用remove。
三、線程的同步與鎖
1、synchronized
public?synchronized?void?xxx()?{
System.out.println(“處理業(yè)務(wù)”);
}
等同于
public?void?xxx()?{
synchronized?(this)?{
System.out.println(“處理業(yè)務(wù)”);
}
}
一般建議使用獨(dú)立的對(duì)象鎖,不要直接用當(dāng)前對(duì)象的鎖,如
public?classSharedData{
private?int?a?=?0;
private?int?b?=?0;
public?synchronized?void?setA(int?a)?{?this.a?=?a;?}
public?synchronized?void?setB(int?b)?{?this.b?=?b;?}
}
若同步整個(gè)方法,則setA()的時(shí)候無法setB(),setB()時(shí)無法setA()。為了提高性能,可以使用不同對(duì)象的鎖:
public?classSharedData{
private?int?a?=?0;
private?int?b?=?0;
private?Object?syncA=?new?Object();
private?Object?syncB=?new?Object();
public?void?setA(int?a)?{
synchronized(syncA)?{
this.a?=?a;
}
}
public?void?setB(int?b)?{
synchronized(syncB)?{
this.b?=?b;
}
}
}
如果將synchronized關(guān)鍵字標(biāo)記在靜態(tài)方法上,由于靜態(tài)方法不可能訪問this實(shí)例,那么,鎖住的是哪個(gè)對(duì)象呢?是當(dāng)前類的Class對(duì)象,原因是每個(gè)對(duì)象的Class實(shí)例是唯一且不可變的。比如:
public?synchronized?static?void?sync()?{?...?}
事實(shí)上完全等同于下面的寫法:
public?static?void?sync()?{
synchronized(SharedData.class)?{
...
}
}
Synchronized的同步方法和代碼塊在多線程中各線程才會(huì)相互競爭對(duì)象鎖,非同步方法不會(huì)競爭對(duì)象鎖。
2、讀寫鎖ReadWriteLock
為了提高性能,Java5提供了讀寫鎖,在讀的地方使用讀鎖,在寫的地方使用寫鎖,靈活控制,在一定程度上提高了程序的執(zhí)行效率。
Java中讀寫鎖有個(gè)接口java.util.concurrent.locks.ReadWriteLock,也有具體的實(shí)現(xiàn)ReentrantReadWriteLock,詳細(xì)的API可以查看JavaAPI文檔。
一般建議使用讀寫鎖ReadWriteLock。
四、線程的調(diào)度
1、wait/(notify/notifyAll())機(jī)制
通常,多線程之間需要協(xié)調(diào)工作。例如,瀏覽器的一個(gè)顯示圖片的線程displayThread想要執(zhí)行顯示圖片的任務(wù),必須等待下載線程?downloadThread將該圖片下載完畢。如果圖片還沒有下載完,displayThread可以暫停,當(dāng)downloadThread完成了任務(wù)后,再通知displayThread“圖片準(zhǔn)備完畢,可以顯示了”,這時(shí),displayThread繼續(xù)執(zhí)行。
以上邏輯簡單的說就是:如果條件不滿足,則等待。當(dāng)條件滿足時(shí),等待該條件的線程將被喚醒。在Java中,這個(gè)機(jī)制的實(shí)現(xiàn)依賴于wait/notify。等待機(jī)制與鎖機(jī)制是密切關(guān)聯(lián)的。例如:
synchronized(obj)?{
while(!condition)?{
obj.wait();
}
obj.doSomething();
}
當(dāng)線程A獲得了obj鎖后,發(fā)現(xiàn)條件condition不滿足,無法繼續(xù)下一處理,于是線程A就wait()。
在另一線程B中,如果B更改了某些條件,使得線程A的condition條件滿足了,就可以喚醒線程A:
synchronized(obj)?{
condition?=?true;
obj.notify();
}
需要注意:
1、調(diào)用obj的wait(),?notify()方法前,必須獲得obj鎖,也就是必須寫在synchronized(obj)?{...}?代碼段內(nèi)。
2、調(diào)用obj.wait()后,線程A就釋放了obj的鎖,否則線程B無法獲得obj鎖,也就無法在synchronized(obj)?{...}?代碼段內(nèi)喚醒A。
3、當(dāng)obj.wait()方法返回后,線程A需要再次獲得obj鎖,才能繼續(xù)執(zhí)行。
4、如果A1,A2,A3都在obj.wait(),則B調(diào)用obj.notify()只能喚醒A1,A2,A3中的一個(gè)(具體哪一個(gè)由JVM決定)。
5、obj.notifyAll()則能全部喚醒A1,A2,A3,但是要繼續(xù)執(zhí)行obj.wait()的下一條語句,必須獲得obj鎖,因此,A1,A2,A3只有一個(gè)有機(jī)會(huì)獲得鎖繼續(xù)執(zhí)行,例如A1,其余的需要等待A1釋放obj鎖之后才能繼續(xù)執(zhí)行。
6、當(dāng)B調(diào)用obj.notify/notifyAll的時(shí)候,B正持有obj鎖,因此,A1,A2,A3雖被喚醒,但是仍無法獲得obj鎖。直到B退出synchronized塊,釋放obj鎖后,A1,A2,A3中的一個(gè)才有機(jī)會(huì)獲得鎖繼續(xù)執(zhí)行。
2、wait/sleep的區(qū)別
Thread還有一個(gè)sleep()靜態(tài)方法也能使線程暫停一段時(shí)間。sleep與wait的不同點(diǎn)是:sleep并不釋放鎖,并且sleep的暫停和wait暫停是不一樣的。obj.wait會(huì)使線程進(jìn)入obj對(duì)象的等待集合中并等待喚醒。但是wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀態(tài),從而使線程立刻拋出InterruptedException。
如果線程A希望立即結(jié)束線程B,則可以對(duì)線程B對(duì)應(yīng)的Thread實(shí)例調(diào)用interrupt方法。如果此刻線程B正在?wait/sleep/join,則線程B會(huì)立刻拋出InterruptedException,在catch()?{}?中直接return即可安全地結(jié)束線程。需要注意的是,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的。對(duì)某一線程調(diào)用?interrupt()時(shí),如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會(huì)拋出InterruptedException。但是,一旦該線程進(jìn)入到?wait()/sleep()/join()后,就會(huì)立刻拋出InterruptedException。
3、線程的讓步y(tǒng)ield()和合并join()
線程的讓步含義就是使當(dāng)前運(yùn)行著線程讓出CPU資源,但是然給誰不知道,僅僅是讓出,線程狀態(tài)回到可運(yùn)行狀態(tài)。
線程的讓步使用Thread.yield()方法,yield()?為靜態(tài)方法,功能是暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。
線程的合并的含義就是將幾個(gè)并行線程的線程合并為一個(gè)單線程執(zhí)行,應(yīng)用場景是當(dāng)一個(gè)線程必須等待另一個(gè)線程執(zhí)行完畢才能執(zhí)行時(shí)可以使用join方法。
五、阻塞隊(duì)列
阻塞隊(duì)列是Java5線程新特征中的內(nèi)容,Java定義了阻塞隊(duì)列的接口java.util.concurrent.BlockingQueue,阻塞隊(duì)列的概念是,一個(gè)指定長度的隊(duì)列,如果隊(duì)列滿了,添加新元素的操作會(huì)被阻塞等待,直到有空位為止。同樣,當(dāng)隊(duì)列為空時(shí)候,請(qǐng)求隊(duì)列元素的操作同樣會(huì)阻塞等待,直到有可用元素為止。
有了這樣的功能為多線程的排隊(duì)等候的業(yè)務(wù)場景開辟了便捷通道,非常有用。
java.util.concurrent.BlockingQueue繼承了java.util.Queue接口,可以參看API文檔。
下面給出一個(gè)簡單應(yīng)用的例子:
publicclassTest?{
publicstaticvoidmain(String[]?args)?throws?InterruptedException?{
BlockingQueue?bqueue?=?new?ArrayBlockingQueue(20);
for(int?i?=?0;?i?<?30;?i++)?{
//將指定元素添加到此隊(duì)列中,如果沒有可用空間,將一直等待(如果有必要)。
bqueue.put(i);
System.out.println("向阻塞隊(duì)列中添加了元素:"?+?i);
}
System.out.println("程序到此運(yùn)行結(jié)束,即將退出----");
}
}
可以看出,輸出到元素19時(shí)候,就一直處于等待狀態(tài),因?yàn)殛?duì)列滿了,程序阻塞了。
另外,阻塞隊(duì)列還有更多實(shí)現(xiàn)類,用來滿足各種復(fù)雜的需求:ArrayBlockingQueue,?DelayQueue,?LinkedBlockingQueue,?PriorityBlockingQueue,?SynchronousQueue?,具體的API差別也很小。
六、線程池和有返回值的線程
Executors:ExecutorService和Future
1、線程池
//創(chuàng)建一個(gè)可重用固定線程數(shù)的線程池
ExecutorService?pool?=Executors.newFixedThreadPool(2);
//創(chuàng)建一個(gè)使用單個(gè)?worker?線程的?Executor,以無界隊(duì)列方式來運(yùn)行該線程。
ExecutorService?pool?=Executors.newSingleThreadExecutor();
//創(chuàng)建一個(gè)可根據(jù)需要?jiǎng)?chuàng)建新線程的線程池,但是在以前構(gòu)造的線程可用時(shí)將重用它們。
ExecutorService?pool?=?Executors.newCachedThreadPool();
//創(chuàng)建一個(gè)線程池,它可安排在給定延遲后運(yùn)行命令或者定期地執(zhí)行。
ScheduledExecutorService?pool?=?Executors.newScheduledThreadPool(2);
//創(chuàng)建一個(gè)單線程執(zhí)行程序,它可安排在給定延遲后運(yùn)行命令或者定期地執(zhí)行。
ScheduledExecutorService?pool?=?Executors.newSingleThreadScheduledExecutor();
//創(chuàng)建等待隊(duì)列
BlockingQueue?bqueue?=?new?ArrayBlockingQueue(20);
//創(chuàng)建一個(gè)單線程執(zhí)行程序,它可安排在給定延遲后運(yùn)行命令或者定期地執(zhí)行。
ThreadPoolExecutor?pool?=?new?ThreadPoolExecutor(2,3,2,TimeUnit.MILLISECONDS,bqueue);
2、有返回值的線程(Future)
Future的主要方法:
boolean?cancel?(boolean?mayInterruptIfRunning)?取消任務(wù)的執(zhí)行。參數(shù)指定是否立即中斷任務(wù)執(zhí)行,或者等等任務(wù)結(jié)束
boolean?isCancelled?()?任務(wù)是否已經(jīng)取消,任務(wù)正常完成前將其取消,則返回?true
boolean?isDone?()?任務(wù)是否已經(jīng)完成。需要注意的是如果任務(wù)正常終止、異常或取消,都將返回true
V?get?()?throws?InterruptedException,?ExecutionException??等待任務(wù)執(zhí)行結(jié)束,然后獲得V類型的結(jié)果。InterruptedException?線程被中斷異常,?ExecutionException任務(wù)執(zhí)行異常,如果任務(wù)被取消,還會(huì)拋出CancellationException
V?get?(long?timeout,?TimeUnit?unit)?throws?InterruptedException,?ExecutionException,?TimeoutException?同上面的get功能一樣,多了設(shè)置超時(shí)時(shí)間。參數(shù)timeout指定超時(shí)時(shí)間,uint指定時(shí)間的單位,在枚舉類TimeUnit中有相關(guān)的定義。如果計(jì)算超時(shí),將拋出TimeoutException
文/阿青(傾力原創(chuàng)),寫代碼寫詩寫職場的程序猿大叔,轉(zhuǎn)載此文請(qǐng)聯(lián)系阿青。