中國福建網(wǎng)

當前位置:中國福建網(wǎng) > 科技 > 正文

300 行代碼帶你秒懂 Java 多線程!

作者: 編輯 來源:互聯(lián)網(wǎng) 發(fā)布時間:2020-06-05

┊文章閱讀:

原標題:300行代碼帶你秒懂Java多線程!

線程

線程的概念,百度是這樣解釋的:

線程(英語:Thread)是操作系統(tǒng)能夠進行運算調(diào)度的最小單位。它被包含在進程之中,是進程中的實際運作單位。一條線程指的是進程中一個單一順序的控制流,一個進程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務。在UnixSystemV及SunOS中也被稱為輕量進程(LightweightProcesses),但輕量進程更多指內(nèi)核線程(KernelThread),而把用戶線程(UserThread)稱為線程。

1.1線程與進程的區(qū)別

進程:指在系統(tǒng)中正在運行的一個應用程序;程序一旦運行就是進程;進程——資源分配的最小單位。

線程:系統(tǒng)分配處理器時間資源的基本單元,或者說進程之內(nèi)獨立執(zhí)行的一個單元執(zhí)行流。線程——程序執(zhí)行的最小單位。

也就是,進程可以包含多個線程,而線程是程序執(zhí)行的最小單位。

1.2線程的狀態(tài)

  • NEW:線程剛創(chuàng)建

  • RUNNABLE:在JVM中正在運行的線程,其中運行狀態(tài)可以有運行中RUNNING和READY兩種狀態(tài),由系統(tǒng)調(diào)度進行狀態(tài)改變。

  • BLOCKED:線程處于阻塞狀態(tài),等待監(jiān)視鎖,可以重新進行同步代碼塊中執(zhí)行

  • WAITING:等待狀態(tài)

  • TIMED_WAITING:調(diào)用sleepjoinwait方法可能導致線程處于等待狀態(tài)

  • TERMINATED:線程執(zhí)行完畢,已經(jīng)退出

1.3Notify和Wait:

Notify和Wait的作用

首先看源碼給出的解釋,這里翻譯了一下:

Notify:喚醒一個正在等待這個對象的線程監(jiān)控。如果有任何線程正在等待這個對象,那么它們中的一個被選擇被喚醒。選擇是任意的,發(fā)生在執(zhí)行的酌情權(quán)。一個線程等待一個對象通過調(diào)用一個{@codewait}方法進行監(jiān)視。

Notify需要在同步方法或同步塊中調(diào)用,即在調(diào)用前,線程也必須獲得該對象的對象級別鎖

Wait:導致當前線程等待,直到另一個線程調(diào)用{@linkjava.lang.Object#notify}方法或{@linkjava.lang.Object#notifyAll}方法。

換句話說,這個方法的行為就像它簡單一樣執(zhí)行調(diào)用{@codewait(0)}。當前線程必須擁有該對象的監(jiān)視器。

線程釋放此監(jiān)視器的所有權(quán),并等待另一個線程通知等待該對象的監(jiān)視器的線程,喚醒通過調(diào)用{@codenotify}方法或{@codenotifyAll}方法。然后線程等待,直到它可以重新取得監(jiān)視器的所有權(quán),然后繼續(xù)執(zhí)行。

Wait的作用是使當前執(zhí)行代碼的線程進行等待,它是Object類的方法,該方法用來將當前線程置入預執(zhí)行隊列中,并且在Wait所在的代碼行處停止執(zhí)行,直到接到通知或被中斷為止。

在調(diào)用Wait方法之前,線程必須獲得該對象的對象級別鎖,即只能在同步方法或同步塊中調(diào)用Wait方法。

Wait和Sleep的區(qū)別:

  • 它們最大本質(zhì)的區(qū)別是,Sleep不釋放同步鎖,Wait釋放同步鎖。

  • 還有用法的上的不同是:Sleep(milliseconds)可以用時間指定來使他自動醒過來,如果時間不到你只能調(diào)用Interreput來強行打斷;Wait可以用Notify直接喚起。

  • 這兩個方法來自不同的類分別是Thread和Object

  • 最主要是Sleep方法沒有釋放鎖,而Wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。

1.4Thread.sleep和Thread.yield的異同

  • 相同:Sleep和yield都會釋放CPU。

  • 不同:Sleep使當前線程進入停滯狀態(tài),所以執(zhí)行Sleep的線程在指定的時間內(nèi)肯定不會執(zhí)行;yield只是使當前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield的線程有可能在進入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。Sleep可使優(yōu)先級低的線程得到執(zhí)行的機會,當然也可以讓同優(yōu)先級和高優(yōu)先級的線程有執(zhí)行的機會;yield只能使同優(yōu)先級的線程有執(zhí)行的機會。

1.5補充:死鎖的概念

死鎖:指兩個或兩個以上的進程(或線程)在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖,這些永遠在互相等待的進程稱為死鎖進程。

死鎖產(chǎn)生的四個必要條件(缺一不可):

  • 互斥條件:顧名思義,線程對資源的訪問是排他性,當該線程釋放資源后下一線程才可進行占用。

  • 請求和保持:簡單來說就是自己拿的不放手又等待新的資源到手。線程T1至少已經(jīng)保持了一個資源R1占用,但又提出對另一個資源R2請求,而此時,資源R2被其他線程T2占用,于是該線程T1也必須等待,但又對自己保持的資源R1不釋放。

  • 不可剝奪:在沒有使用完資源時,其他線性不能進行剝奪。

  • 循環(huán)等待:一直等待對方線程釋放資源。

我們可以根據(jù)死鎖的四個必要條件破壞死鎖的形成。

1.6補充:并發(fā)和并行的區(qū)別

并發(fā):是指在某個時間段內(nèi),多任務交替的執(zhí)行任務。當有多個線程在操作時,把CPU運行時間劃分成若干個時間段,再將時間段分配給各個線程執(zhí)行。在一個時間段的線程代碼運行時,其它線程處于掛起狀。

并行:是指同一時刻同時處理多任務的能力。當有多個線程在操作時,CPU同時處理這些線程請求的能力。

區(qū)別就在于CPU是否能同時處理所有任務,并發(fā)不能,并行能。

1.7補充:線程安全三要素

  • 原子性:Atomic包、CAS算法、Synchronized、Lock。

  • 可見性:Synchronized、Volatile(不能保證原子性)。

  • 有序性:Happens-before規(guī)則。

1.8補充:如何實現(xiàn)線程安全

  • 互斥同步:Synchronized、Lock。

  • 非阻塞同步:CAS。

  • 無需同步的方案:如果一個方法本來就不涉及共享數(shù)據(jù),那它自然就無需任何同步操作去保證正確性。

1.9補充:保證線程安全的機制:

  • Synchronized關(guān)鍵字

  • Lock

  • CAS、原子變量

  • ThreadLocl:簡單來說就是讓每個線程,對同一個變量,都有自己的獨有副本,每個線程實際訪問的對象都是自己的,自然也就不存在線程安全問題了。

  • Volatile

  • CopyOnWrite寫時復制

隨著CPU核心的增多以及互聯(lián)網(wǎng)迅速發(fā)展,單線程的程序處理速度越來越跟不上發(fā)展速度和大數(shù)據(jù)量的增長速度,多線程應運而生,充分利用CPU資源的同時,極大提高了程序處理速度。

創(chuàng)建線程的方法

繼承Thread類:

publicclassThreadCreateTest{

publicstaticvoidmain(String[]args){

newMyThread.start;

}

}

classMyThreadextendsThread{

@Override

publicvoidrun{

System.out.println(Thread.currentThread.getName+"t"+Thread.currentThread.getId);

}

}

實現(xiàn)Runable接口:

publicclassRunableCreateTest{

publicstaticvoidmain(String[]args){

MyRunnablerunnable=newMyRunnable;

newThread(runnable).start;

}

}

classMyRunnableimplementsRunnable{

@Override

publicvoidrun{

System.out.println(Thread.currentThread.getName+"t"+Thread.currentThread.getId);

}

}

通過Callable和Future創(chuàng)建線程:

publicclassCallableCreateTest{

publicstaticvoidmain(String[]args)throwsException{

//將Callable包裝成FutureTask,F(xiàn)utureTask也是一種Runnable

MyCallablecallable=newMyCallable;

FutureTask<Integer>futureTask=newFutureTask<>(callable);

newThread(futureTask).start;

//get方法會阻塞調(diào)用的線程

Integersum=futureTask.get;

System.out.println(Thread.currentThread.getName+Thread.currentThread.getId+"="+sum);

}

}

classMyCallableimplementsCallable<Integer>{

@Override

publicIntegercallthrowsException{

System.out.println(Thread.currentThread.getName+"t"+Thread.currentThread.getId+"t"+newDate+"tstarting...");

intsum=0;

for(inti=0;i<=100000;i++){

sum+=i;

}

Thread.sleep(5000);

System.out.println(Thread.currentThread.getName+"t"+Thread.currentThread.getId+"t"+newDate+"tover...");

returnsum;

}

}

線程池方式創(chuàng)建:

實現(xiàn)Runnable接口這種方式更受歡迎,因為這不需要繼承Thread類。在應用設計中已經(jīng)繼承了別的對象的情況下,這需要多繼承(而Java不支持多繼承,但可以多實現(xiàn)啊),只能實現(xiàn)接口。同時,線程池也是非常高效的,很容易實現(xiàn)和使用。

實際開發(fā)中,阿里巴巴開發(fā)插件一直提倡使用線程池創(chuàng)建線程,原因在下方會解釋,所以上面的代碼我就只簡寫了一些Demo。

2.1線程池創(chuàng)建線程

線程池,顧名思義,線程存放的地方。和數(shù)據(jù)庫連接池一樣,存在的目的就是為了較少系統(tǒng)開銷,主要由以下幾個特點:

降低資源消耗。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗(主要)。

提高響應速度。當任務到達時,任務可以不需要等到線程創(chuàng)建就能立即執(zhí)行。

提高線程的可管理性。線程是稀缺資源,如果無限制地創(chuàng)建,不僅會消耗系統(tǒng)資源,還會降低系統(tǒng)的穩(wěn)定性。

Java提供四種線程池創(chuàng)建方式:

  • newCachedThreadPool創(chuàng)建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則新建線程。

  • newFixedThreadPool創(chuàng)建一個定長線程池,可控制線程最大并發(fā)數(shù),超出的線程會在隊列中等待。

  • newScheduledThreadPool創(chuàng)建一個定長線程池,支持定時及周期性任務執(zhí)行。

  • newSingleThreadExecutor創(chuàng)建一個單線程化的線程池,它只會用唯一的工作線程來執(zhí)行任務,保證所有任務按照指定順序(FIFO,LIFO,優(yōu)先級)執(zhí)行。

通過源碼我們得知ThreadPoolExecutor繼承自AbstractExecutorService,而AbstractExecutorService實現(xiàn)了ExecutorService。

publicclassThreadPoolExecutorextendsAbstractExecutorService

publicabstractclassAbstractExecutorServiceimplementsExecutorService

2.2ThreadPoolExecutor介紹

實際項目中,用的最多的就是ThreadPoolExecutor這個類,而《阿里巴巴Java開發(fā)手冊》中強制線程池不允許使用Executors去創(chuàng)建,而是通過NewThreadPoolExecutor實例的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險。

我們從ThreadPoolExecutor入手多線程創(chuàng)建方式,先看一下線程池創(chuàng)建的最全參數(shù)。

publicThreadPoolExecutor(intcorePoolSize,

intmaximumPoolSize,

longkeepAliveTime,

TimeUnitunit,

BlockingQueue<Runnable>workQueue,

ThreadFactorythreadFactory,

RejectedExecutionHandlerhandler){

if(corePoolSize<0||

maximumPoolSize<=0||

maximumPoolSize<corePoolSize||

keepAliveTime<0)

thrownewIllegalArgumentException;

if(workQueue==null||threadFactory==null||handler==null)

thrownewNullPointerException;

this.corePoolSize=corePoolSize;

this.maximumPoolSize=maximumPoolSize;

this.workQueue=workQueue;

this.keepAliveTime=unit.toNanos(keepAliveTime);

this.threadFactory=threadFactory;

this.handler=handler;

}

參數(shù)說明如下:

  • corePoolSize:線程池的核心線程數(shù),即便線程池里沒有任何任務,也會有corePoolSize個線程在候著等任務。

  • maximumPoolSize:最大線程數(shù),不管提交多少任務,線程池里最多工作線程數(shù)就是maximumPoolSize。

  • keepAliveTime:線程的存活時間。當線程池里的線程數(shù)大于corePoolSize時,如果等了keepAliveTime時長還沒有任務可執(zhí)行,則線程退出。

  • Unit:這個用來指定keepAliveTime的單位,比如秒:TimeUnit.SECONDS。

  • BlockingQueue:一個阻塞隊列,提交的任務將會被放到這個隊列里。

  • threadFactory:線程工廠,用來創(chuàng)建線程,主要是為了給線程起名字,默認工廠的線程名字:pool-1-thread-3。

  • handler:拒絕策略,當線程池里線程被耗盡,且隊列也滿了的時候會調(diào)用。

2.2.1BlockingQueue

對于BlockingQueue個人感覺還需要單獨拿出來說一下。

BlockingQueue:阻塞隊列,有先進先出(注重公平性)和先進后出(注重時效性)兩種,常見的有兩種阻塞隊列:ArrayBlockingQueue和LinkedBlockingQueue

隊列的數(shù)據(jù)結(jié)構(gòu)大致如圖:

隊列一端進入,一端輸出。而當隊列滿時,阻塞。BlockingQueue核心方法:1.放入數(shù)據(jù)put2.獲取數(shù)據(jù)take。常見的兩種Queue:

2.2.2ArrayBlockingQueue

基于數(shù)組實現(xiàn),在ArrayBlockingQueue內(nèi)部,維護了一個定長數(shù)組,以便緩存隊列中的數(shù)據(jù)對象,這是一個常用的阻塞隊列,除了一個定長數(shù)組外,ArrayBlockingQueue內(nèi)部還保存著兩個整形變量,分別標識著隊列的頭部和尾部在數(shù)組中的位置。

一段代碼來驗證一下:

packagemap;

importjava.util.concurrent.*;

publicclassMyTestMap{

//定義阻塞隊列大小

privatestaticfinalintmaxSize=5;

publicstaticvoidmain(String[]args){

ArrayBlockingQueue<Integer>queue=newArrayBlockingQueue<Integer>(maxSize);

newThread(newProductor(queue)).start;

newThread(newCustomer(queue)).start;

}

}

classCustomerimplementsRunnable{

privateBlockingQueue<Integer>queue;

Customer(BlockingQueue<Integer>queue){

this.queue=queue;

}

@Override

publicvoidrun{

this.cusume;

}

privatevoidcusume{

while(true){

try{

intcount=(int)queue.take;

System.out.println("customer正在消費第"+count+"個商品===");

//只是為了方便觀察輸出結(jié)果

Thread.sleep(10);

}catch(InterruptedExceptione){

e.printStackTrace;

}

}

}

}

classProductorimplementsRunnable{

privateBlockingQueue<Integer>queue;

privateintcount=1;

Productor(BlockingQueue<Integer>queue){

this.queue=queue;

}

@Override

publicvoidrun{

this.product;

}

privatevoidproduct{

while(true){

try{

queue.put(count);

System.out.println("生產(chǎn)者正在生產(chǎn)第"+count+"個商品");

count++;

}catch(InterruptedExceptione){

e.printStackTrace;

}

}

}

}

//輸出如下

/**

生產(chǎn)者正在生產(chǎn)第1個商品

生產(chǎn)者正在生產(chǎn)第2個商品

生產(chǎn)者正在生產(chǎn)第3個商品

生產(chǎn)者正在生產(chǎn)第4個商品

生產(chǎn)者正在生產(chǎn)第5個商品

customer正在消費第1個商品===

*/

2.2.3LinkedBlockingQueue

基于鏈表的阻塞隊列,內(nèi)部也維護了一個數(shù)據(jù)緩沖隊列。需要我們注意的是如果構(gòu)造一個LinkedBlockingQueue對象,而沒有指定其容量大小。

LinkedBlockingQueue會默認一個類似無限大小的容量(Integer.MAX_VALUE),這樣的話,如果生產(chǎn)者的速度一旦大于消費者的速度,也許還沒有等到隊列滿阻塞產(chǎn)生,系統(tǒng)內(nèi)存就有可能已被消耗殆盡了。

2.2.4LinkedBlockingQueue和ArrayBlockingQueue的主要區(qū)別

  • ArrayBlockingQueue的初始化必須傳入隊列大小,LinkedBlockingQueue則可以不傳入。

  • ArrayBlockingQueue用一把鎖控制并發(fā),LinkedBlockingQueue倆把鎖控制并發(fā),鎖的細粒度更細。即前者生產(chǎn)者消費者進出都是一把鎖,后者生產(chǎn)者生產(chǎn)進入是一把鎖,消費者消費是另一把鎖。

  • ArrayBlockingQueue采用數(shù)組的方式存取,LinkedBlockingQueue用Node鏈表方式存取。

2.2.5handler拒絕策略

Java提供了4種丟棄處理的方法,當然你也可以自己實現(xiàn),主要是要實現(xiàn)接口:RejectedExecutionHandler中的方法。

  • AbortPolicy:不處理,直接拋出異常。

  • CallerRunsPolicy:只用調(diào)用者所在線程來運行任務,即提交任務的線程。

  • DiscardOldestPolicy:LRU策略,丟棄隊列里最近最久不使用的一個任務,并執(zhí)行當前任務。

  • DiscardPolicy:不處理,丟棄掉,不拋出異常。

2.2.6線程池五種狀態(tài)

privatestaticfinalintRUNNING=-1<<COUNT_BITS;

privatestaticfinalintSHUTDOWN=0<<COUNT_BITS;

privatestaticfinalintSTOP=1<<COUNT_BITS;

privatestaticfinalintTIDYING=2<<COUNT_BITS;

privatestaticfinalintTERMINATED=3<<COUNT_BITS;

RUNNING:在這個狀態(tài)的線程池能判斷接受新提交的任務,并且也能處理阻塞隊列中的任務。

SHUTDOWN:處于關(guān)閉的狀態(tài),該線程池不能接受新提交的任務,但是可以處理阻塞隊列中已經(jīng)保存的任務,在線程處于RUNNING狀態(tài),調(diào)用shutdown方法能切換為該狀態(tài)。

STOP:線程池處于該狀態(tài)時既不能接受新的任務也不能處理阻塞隊列中的任務,并且能中斷現(xiàn)在線程中的任務。當線程處于RUNNING和SHUTDOWN狀態(tài),調(diào)用shutdownNow方法就可以使線程變?yōu)樵摖顟B(tài)。

TIDYING:在SHUTDOWN狀態(tài)下阻塞隊列為空,且線程中的工作線程數(shù)量為0就會進入該狀態(tài),當在STOP狀態(tài)下時,只要線程中的工作線程數(shù)量為0就會進入該狀態(tài)。

TERMINATED:在TIDYING狀態(tài)下調(diào)用terminated方法就會進入該狀態(tài)??梢哉J為該狀態(tài)是最終的終止狀態(tài)。

回到線程池創(chuàng)建ThreadPoolExecutor,我們了解了這些參數(shù),再來看看ThreadPoolExecutor的內(nèi)部工作原理:

  • 判斷核心線程是否已滿,是進入隊列,否:創(chuàng)建線程

  • 判斷等待隊列是否已滿,是:查看線程池是否已滿,否:進入等待隊列

  • 查看線程池是否已滿,是:拒絕,否創(chuàng)建線程

2.3深入理解ThreadPoolExecutor

進入Execute方法可以看到:

publicvoidexecute(Runnablecommand){

if(command==null)

thrownewNullPointerException;

intc=ctl.get;

//判斷當前活躍線程數(shù)是否小于corePoolSize,如果小于,則調(diào)用addWorker創(chuàng)建線程執(zhí)行任務

if(workerCountOf(c)<corePoolSize){

if(addWorker(command,true))

return;

c=ctl.get;

}

//如果不小于corePoolSize,則將任務添加到workQueue隊列。

if(isRunning(c)&&workQueue.offer(command)){

intrecheck=ctl.get;

if(!isRunning(recheck)&&remove(command))

reject(command);

elseif(workerCountOf(recheck)==0)

addWorker(null,false);

}

//如果放入workQueue失敗,則創(chuàng)建線程執(zhí)行任務,如果這時創(chuàng)建線程失敗(當前線程數(shù)不小于maximumPoolSize時),就會調(diào)用reject(內(nèi)部調(diào)用handler)拒絕接受任務。

elseif(!addWorker(command,false))

reject(command);

}

AddWorker方法:

  • 創(chuàng)建Worker對象,同時也會實例化一個Thread對象。在創(chuàng)建Worker時會調(diào)用threadFactory來創(chuàng)建一個線程。

  • 然后啟動這個線程。

2.3.1線程池中CTL屬性的作用是什么?

看源碼第一反應就是這個CTL到底是個什么東東?有啥用?一番研究得出如下結(jié)論:

CTL屬性包含兩個概念:

privatefinalAtomicIntegerctl=newAtomicInteger(ctlOf(RUNNING,0));

privatestaticintctlOf(intrs,intwc){returnrs|wc;}

  • runState:即rs表明當前線程池的狀態(tài),是否處于Running,Shutdown,Stop,Tidying。

  • workerCount:即wc表明當前有效的線程數(shù)。

我們點擊workerCount即工作狀態(tài)記錄值,以RUNNING為例,RUNNING=-1<<COUNT_BITS;,即-1無符號左移COUNT_BITS位,進一步我們得知COUNT_BITS位29,因為Integer位數(shù)為31位(2的五次方減一)

privatestaticfinalintCOUNT_BITS=Integer.SIZE-3;

既然是29位那么就是Running的值為:

11100000000000000000000000000000

|||

31~29位

那低28位呢,就是記錄當前線程的總線數(shù)啦:

//Packingandunpackingctl

privatestaticintrunStateOf(intc){returnc&~CAPACITY;}

privatestaticintworkerCountOf(intc){returnc&CAPACITY;}

privatestaticintctlOf(intrs,intwc){returnrs|wc;}

從上述代碼可以看到workerCountOf這個函數(shù)傳入ctl之后,是通過CTL&CAPACITY操作來獲取當前運行線程總數(shù)的。

也就是RunningState|WorkCount&CAPACITY,算出來的就是低28位的值。因為CAPACITY得到的就是高3位(29-31位)位0,低28位(0-28位)都是1,所以得到的就是ctl中低28位的值。

而runStateOf這個方法的話,算的就是RunningState|WorkCount&CAPACITY,高3位的值,因為CAPACITY是CAPACITY的取反,所以得到的就是高3位(29-31位)為1,低28位(0-28位)為0,所以通過&運算后,所得到的值就是高3為的值。

簡單來說就是ctl中是高3位作為狀態(tài)值,低28位作為線程總數(shù)值來進行存儲。

2.3.2shutdownNow和shutdown的區(qū)別

看源碼發(fā)現(xiàn)有兩種近乎一樣的方法,shutdownNow和shutdown,設計者這么設計自然是有它的道理,那么這兩個方法的區(qū)別在哪呢?

  • shutdown會把線程池的狀態(tài)改為SHUTDOWN,而shutdownNow把當前線程池狀態(tài)改為STOP。

  • shutdown只會中斷所有空閑的線程,而shutdownNow會中斷所有的線程。

  • shutdown返回方法為空,會將當前任務隊列中的所有任務執(zhí)行完畢;而shutdownNow把任務隊列中的所有任務都取出來返回。

2.3.3線程復用原理

finalvoidrunWorker(Workerw){

Threadwt=Thread.currentThread;

Runnabletask=w.firstTask;

w.firstTask=null;

w.unlock;//allowinterrupts

booleancompletedAbruptly=true;

try{

while(task!=null||(task=getTask)!=null){

w.lock;

//Ifpoolisstopping,ensurethreadisinterrupted;

//ifnot,ensurethreadisnotinterrupted.This

//requiresarecheckinsecondcasetodealwith

//shutdownNowracewhileclearinginterrupt

if((runStateAtLeast(ctl.get,STOP)||

(Thread.interrupted&&

runStateAtLeast(ctl.get,STOP)))&&

!wt.isInterrupted)

wt.interrupt;

try{

beforeExecute(wt,task);

Throwablethrown=null;

try{

task.run;

}catch(RuntimeExceptionx){

thrown=x;throwx;

}catch(Errorx){

thrown=x;throwx;

}catch(Throwablex){

thrown=x;thrownewError(x);

}finally{

afterExecute(task,thrown);

}

}finally{

task=null;

w.completedTasks++;

w.unlock;

}

}

completedAbruptly=false;

}finally{

processWorkerExit(w,completedAbruptly);

}

}

就是任務在并不只執(zhí)行創(chuàng)建時指定的firstTask第一任務,還會從任務隊列的中自己主動取任務執(zhí)行,而且是有或者無時間限定的阻塞等待,以保證線程的存活。

默認的是不允許。

2.4CountDownLatch和CyclicBarrier區(qū)別

countDownLatch是一個計數(shù)器,線程完成一個記錄一個,計數(shù)器遞減,只能只用一次。

CyclicBarrier的計數(shù)器更像一個閥門,需要所有線程都到達,然后繼續(xù)執(zhí)行,計數(shù)器遞增,提供Reset功能,可以多次使用。

3.多線程間通信的幾種方式

提及多線程又不得不提及多線程通信的機制。首先,要短信線程間通信的模型有兩種:共享內(nèi)存和消息傳遞,以下方式都是基本這兩種模型來實現(xiàn)的。我們來基本一道面試常見的題目來分析:

題目:有兩個線程A、B,A線程向一個集合里面依次添加元素"abc"字符串,一共添加十次,當添加到第五次的時候,希望B線程能夠收到A線程的通知,然后B線程執(zhí)行相關(guān)的業(yè)務操作。

3.1使用volatile關(guān)鍵字

packagethread;

/**

*

*@authorhxz

*@deion多線程測試類

*@version1.0

*@data2020年2月15日上午9:10:09

*/

publicclassMyThreadTest{

publicstaticvoidmain(String[]args)throwsException{

notifyThreadWithVolatile;

}

/**

*定義一個測試

*/

privatestaticvolatilebooleanflag=false;

/**

*計算I++,當I==5時,通知線程B

*@throwsException

*/

privatestaticvoidnotifyThreadWithVolatilethrowsException{

Threadthc=newThread("線程A"){

@Override

publicvoidrun{

for(inti=0;i<10;i++){

if(i==5){

flag=true;

try{

Thread.sleep(500L);

}catch(InterruptedExceptione){

//TODOAuto-generatedcatchblock

e.printStackTrace;

}

break;

}

System.out.println(Thread.currentThread.getName+"===="+i);

}

}

};

Threadthd=newThread("線程B"){

@Override

publicvoidrun{

while(true){

//防止偽喚醒所以使用了while

while(flag){

System.out.println(Thread.currentThread.getName+"收到通知");

System.out.println("dosomething");

try{

Thread.sleep(500L);

}catch(InterruptedExceptione){

//TODOAuto-generatedcatchblock

e.printStackTrace;

}

return;

}

}

}

};

thd.start;

Thread.sleep(1000L);

thc.start;

}

}

個人認為這是基本上最好的通信方式,因為A發(fā)出通知B能夠立馬接受并DoSomething。

原文鏈接:

https://blog.csdn.net/weixin_44104367/article/details/104481510

  • 分類目錄
  • 軟文發(fā)布平臺
  • 勞務外包公司
  • 帆布水池
  • 運維開發(fā)網(wǎng)
  • 小程序開發(fā)
  • 淘寶優(yōu)惠券
  • IT新聞
  • 淘寶erp
  • 植物提取物網(wǎng)
  • 站長網(wǎng)
  • 源碼論壇
  • 激光打標機
  • 丹泊儀器
  • 礦山生態(tài)修復
  • 青島月子會所
  • 知識付費
  • 辦公家具
  • 呱呱贊小程序
  • 淄博java培訓
  • 小程序開發(fā)
  • seo外包公司
  • 盈江新財網(wǎng)
  • 工程拍照軟件
  • 速賣通論壇
  • 極客網(wǎng)
  • 甘州文化網(wǎng)
  • 優(yōu)鞋論壇
  • 寧波小程序開發(fā)
  • 域名論壇
  • 微軟crm
  • andon系統(tǒng)
  • 鄭州網(wǎng)站建設
  • seo學習網(wǎng)
  • 奢侈品回收
  • 一對一輔導
  • 黑客視野新聞