十分鐘掌握java多線程進(jìn)階

一、線程的實(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)系阿青。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,596評(píng)論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,107評(píng)論 1 18
  • 該文章轉(zhuǎn)自:http://blog.csdn.net/evankaka/article/details/44153...
    加來依藍(lán)閱讀 7,467評(píng)論 3 87
  • 寫在前面的話: 這篇博客是我從這里“轉(zhuǎn)載”的,為什么轉(zhuǎn)載兩個(gè)字加“”呢?因?yàn)檫@絕不是簡單的復(fù)制粘貼,我花了五六個(gè)小...
    SmartSean閱讀 4,936評(píng)論 12 45
  • 我待在家里的時(shí)候本來想著晚上自己出去吃點(diǎn)東西,因?yàn)榭赡馨职謺?huì)回來的晚一些,爸爸還在收拾。 他像是看穿了我的心思,爸...
    愛夢的我閱讀 145評(píng)論 0 0

友情鏈接更多精彩內(nèi)容