執行緒的概念,百度是這樣解說的:
執行緒(英語:Thread)是操作體系可以進行運算調度的最小單位,它被包括在行程之中,是行程中的實際運作單位,一條執行緒指的是行程中一個單一次序的操控流,一個行程中可以并發多個執行緒,每條執行緒并行履行不同的使命,在UnixSystemV及SunOS中也被稱為輕量行程(LightweightProcesses),但輕量行程更多指內核執行緒(KernelThread),而把用戶執行緒(UserThread)稱為執行緒,
1.1執行緒與行程的差異
行程:指在體系中正在運轉的一個運用程式;程式一旦運轉便是行程;行程——資源分配的最小單位,
執行緒:體系分配處理器時間資源的基本單元,或許說行程之內獨立履行的一個單元履行流,執行緒——程式履行的最小單位,
也便是,行程可以包括多個執行緒,而執行緒是程式履行的最小單位,
1.2執行緒的狀況
NEW:執行緒剛創立
RUNNABLE:在JVM中正在運轉的執行緒,其間運轉狀況可以有運轉中RUNNING和READY兩種狀況,由體系調度進行狀況改變,
BLOCKED:執行緒處于堵塞狀況,等候監督鎖,可以重新進行同步代碼塊中履行
WAITING:等候狀況
TIMED_WAITING:呼叫sleepjoinwait辦法或許導致執行緒處于等候狀況
TERMINATED:執行緒履行完畢,現已退出
1.3Notify和Wait:
Notify和Wait的效果
首要看原始碼給出的解說,這里翻譯了一下:
Notify:喚醒一個正在等候這個目標的執行緒監控,假如有任何執行緒正在等候這個目標,那么它們中的一個被選擇被喚醒,選擇是任意的,發生在履行的酌情權,一個執行緒等候一個目標經過呼叫一個{@codewait}辦法進行監督,
Notify需求在同步辦法或同步塊中呼叫,即在呼叫前,執行緒也必須獲得該目標的目標等級鎖
Wait:導致當時執行緒等候,直到另一個執行緒呼叫{@linkjava.lang.Object#notify}辦法或{@linkjava.lang.Object#notifyAll}辦法,
換句話說,這個辦法的行為就像它簡略相同履行呼叫{@codewait(0)},當時執行緒必須擁有該目標的監督器,
執行緒開釋此監督器的一切權,并等候另一個執行緒告訴等候該目標的監督器的執行緒,喚醒經過呼叫{@codenotify}辦法或{@codenotifyAll}辦法,然后執行緒等候,直到它可以重新獲得監督器的一切權,然后持續履行,
Wait的效果是使當時履行代碼的執行緒進行等候,它是Object類的辦法,該辦法用來將當時執行緒置入預履行行列中,并且在Wait地點的代碼行處中止履行,直到接到告訴或被中止為止,
在呼叫Wait辦法之前,執行緒必須獲得該目標的目標等級鎖,即只能在同步辦法或同步塊中呼叫Wait辦法,
Wait和Sleep的差異:
它們最大本質的差異是,Sleep不開釋同步鎖,Wait開釋同步鎖,
還有用法的上的不同是:Sleep(milliseconds)可以用時間指定來使他主動醒過來,假如時間不到你只能呼叫Interreput來強行打斷;Wait可以用Notify直接喚起,
這兩個辦法來自不同的類分別是Thread和Object
最首要是Sleep辦法沒有開釋鎖,而Wait辦法開釋了鎖,使得其他執行緒可以運用同步操控塊或許辦法,
1.4Thread.sleep和Thread.yield的異同
相同:Sleep和yield都會開釋CPU,
不同:Sleep使當時執行緒進入阻滯狀況,所以履行Sleep的執行緒在指定的時間內必定不會履行;yield僅僅使當時執行緒重新回到可履行狀況,所以履行yield的執行緒有或許在進入到可履行狀況后立刻又被履行,Sleep可使優先級低的執行緒得到履行的時機,當然也可以讓同優先級和高優先級的執行緒有履行的時機;yield只能使同優先級的執行緒有履行的時機,
1.5彌補:死鎖的概念
死鎖:指兩個或兩個以上的行程(或執行緒)在履行程序中,因搶奪資源而形成的一種互相等候的現象,若無外力效果,它們都將無法推動下去,此刻稱體系處于死鎖狀況或體系發生了死鎖,這些永遠在互相等候的行程稱為死鎖行程,
死鎖發生的四個必要條件(缺一不可):
互斥條件:望文生義,執行緒對資源的拜訪是排他性,當該執行緒開釋資源后下一執行緒才可進行占用,
懇求和堅持:簡略來說便是自己拿的不放手又等候新的資源到手,執行緒T1至少現已堅持了一個資源R1占用,但又提出對另一個資源R2懇求,而此刻,資源R2被其他執行緒T2占用,于是該執行緒T1也必須等候,但又對自己堅持的資源R1不開釋,
不可掠奪:在沒有運用完資源時,其他線性不能進行掠奪,
回圈等候:一向等候對方執行緒開釋資源,
咱們可以根據死鎖的四個必要條件損壞死鎖的形成,
1.6彌補:并發和并行的差異
并發:是指在某個時間段內,多使命替換的履行使命,當有多個執行緒在操作時,把CPU運轉時間劃分成若干個時間段,再將時間段分配給各個執行緒履行,在一個時間段的執行緒代碼運轉時,其它執行緒處于掛起狀,
并行:是指同一時間一起處理多使命的才能,當有多個執行緒在操作時,CPU一起處理這些執行緒懇求的才能,
差異就在于CPU是否能一起處理一切使命,并發不能,并行能,
1.7彌補:執行緒安全三要素
原子性:Atomic包、CAS演算法、Synchronized、Lock,
可見性:Synchronized、Volatile(不能確保原子性),
有序性:Happens-before規矩,
1.8彌補:怎么完成執行緒安全
互斥同步:Synchronized、Lock,
非堵塞同步:CAS,
無需同步的方案:假如一個辦法原本就不涉及共享資料,那它天然就無需任何同步操作去確保正確性,
1.9彌補:確保執行緒安全的機制:
Synchronized關鍵字
Lock
CAS、原子變數
ThreadLocl:簡略來說便是讓每個執行緒,對同一個變數,都有自己的獨有副本,每個執行緒實際拜訪的目標都是自己的,天然也就不存在執行緒安全問題了,
Volatile
CopyOnWrite寫時復制
隨著CPU中心的增多以及互聯網迅速發展,單執行緒的程式處理速度越來越跟不上發展速度和大資料量的增長速度,多執行緒應運而生,充分利用CPU資源的一起,極大進步了程式處理速度,
創立執行緒的辦法
承繼Thread類:
publicclassThreadCreateTest{
publicstaticvoidmain(String[]args){
newMyThread.start;
}
}
classMyThreadextendsThread{
@Override
publicvoidrun{
System.out.println(Thread.currentThread.getName+"t"+Thread.currentThread.getId);
}
}
完成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創立執行緒:
publicclassCallableCreateTest{
publicstaticvoidmain(String[]args)throwsException{
//將Callable包裝成FutureTask,FutureTask也是一種Runnable
MyCallablecallable=newMyCallable;
FutureTaskfutureTask=newFutureTask<>(callable);
newThread(futureTask).start;
//get辦法會堵塞呼叫的執行緒
Integersum=futureTask.get;
System.out.println(Thread.currentThread.getName+Thread.currentThread.getId+"="+sum);
}
}
classMyCallableimplementsCallable{
@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;
}
}
執行緒池辦法創立:
完成Runnable介面這種辦法更受歡迎,由于這不需求承繼Thread類,在運用規劃中現已承繼了其他目標的情況下,這需求多承繼(而Java不支撐多承繼,但可以多完成啊),只能完成介面,一起,執行緒池也是十分高效的,很容易完成和運用,
實際開發中,阿里巴巴開發插件一向提倡運用執行緒池創立執行緒,原因在下方會解說,所以上面的代碼我就只簡寫了一些Demo,
2.1執行緒池創立執行緒
執行緒池,望文生義,執行緒寄存的當地,和資料庫連接池相同,存在的目的便是為了較少體系開銷,首要由以下幾個特點:
下匠澩耗費,經過重復利用已創立的執行緒下降執行緒創立和毀掉形成的耗費(首要),
進步回應速度,當使命抵達時,使命可以不需求比及執行緒創立就能立即履行,
進步執行緒的可管理性,執行緒是稀缺資源,假如無限制地創立,不只會耗費體銑澩,還會下降體系的穩定性,
Java供給四種執行緒池創立辦法:
newCachedThreadPool創立一個可快取執行緒池,假如執行緒池長度超越處理需求,可靈敏識訓空閑執行緒,若無可識訓,則新建執行緒,
newFixedThreadPool創立一個定長執行緒池,可操控線程最大并發數,超出的執行緒會在行列中等候,
newScheduledThreadPool創立一個定長執行緒池,支撐定時及周期性使命履行,
newSingleThreadExecutor創立一個單執行緒化的執行緒池,它只會用僅有的作業執行緒來履行使命,確保一切使命按照指定次序(FIFO,LIFO,優先級)履行,
經過原始碼咱們得知ThreadPoolExecutor承繼自AbstractExecutorService,而AbstractExecutorService完成了ExecutorService,
publicclassThreadPoolExecutorextendsAbstractExecutorService
publicabstractclassAbstractExecutorServiceimplementsExecutorService
2.2ThreadPoolExecutor介紹
實際專案中,用的最多的便是ThreadPoolExecutor這個類,而《阿里巴巴Java開發手冊》中強制執行緒池不允許運用Executors去創立,而是經過NewThreadPoolExecutor實體的辦法,這樣的處理辦法讓寫的同學更加明確執行緒池的運轉規矩,規避資源耗盡的危險,
咱們從ThreadPoolExecutor下手多執行緒創立辦法,先看一下執行緒池創立的最全引數,
publicThreadPoolExecutor(intcorePoolSize,
intmaximumPoolSize,
longkeepAliveTime,
TimeUnitunit,
BlockingQueueworkQueue,
ThreadFactorythreadFactory,
RejectedExecutionHandlerhandler){
if(corePoolSize<0||
maximumPoolSize<=0||
maximumPoolSize
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;
}
引數闡明如下:
corePoolSize:執行緒池的中心執行緒數,即便執行緒池里沒有任何使命,也會有corePoolSize個執行緒在候著等使命,
maximumPoolSize:最大執行緒數,不論提交多少使命,執行緒池里最多作業執行緒數便是maximumPoolSize,
keepAliveTime:執行緒的存活時間,當執行緒池里的執行緒數大于corePoolSize時,假如等了keepAliveTime時長還沒有使命可履行,則執行緒退出,
Unit:這個用來指定keepAliveTime的單位,比如秒:TimeUnit.SECONDS,
BlockingQueue:一個堵塞行列,提交的使命將會被放到這個行列里,
threadFactory:執行緒工廠,用來創立執行緒,首要是為了給執行緒起名字,默許工廠的執行緒名字:pool-1-thread-3,
handler:回絕戰略,當執行緒池里執行緒被耗盡,且行列也滿了的時分會呼叫,
2.2.1BlockingQueue
對于BlockingQueue個人感徑訓需求單獨拿出來說一下,
BlockingQueue:堵塞行列,有先進先出(重視公平性)和先進后出(重視時效性)兩種,常見的有兩種堵塞行列:ArrayBlockingQueue和LinkedBlockingQueue
行列的資料結構大致如圖:
行列一端進入,一端輸出,而當行列滿時,堵塞,BlockingQueue中心辦法:1.放入資料put2.獲取資料take,常見的兩種Queue:
2.2.2ArrayBlockingQueue
基于陣列完成,在ArrayBlockingQueue內部,保護了一個定長陣列,以便快取行列中的資料目標,這是一個常用的堵塞行列,除了一個定長陣列外,ArrayBlockingQueue內部還保存著兩個整形變數,分別標識著行列的頭部和尾部在陣列中的位置,
一段代碼來驗證一下:
packagemap;
importjava.util.concurrent.*;
publicclassMyTestMap{
//界說堵塞行列巨細
privatestaticfinalintmaxSize=5;
publicstaticvoidmain(String[]args){
ArrayBlockingQueuequeue=newArrayBlockingQueue(maxSize);
newThread(newProductor(queue)).start;
newThread(newCustomer(queue)).start;
}
}
classCustomerimplementsRunnable{
privateBlockingQueuequeue;
Customer(BlockingQueuequeue){
this.queue=queue;
}
@Override
publicvoidrun{
this.cusume;
}
privatevoidcusume{
while(true){
try{
intcount=(int)queue.take;
System.out.println("customer正在消費第"+count+"個產品===");
//僅僅為了便利觀察輸出成果
Thread.sleep(10);
}catch(InterruptedExceptione){
e.printStackTrace;
}
}
}
}
classProductorimplementsRunnable{
privateBlockingQueuequeue;
privateintcount=1;
Productor(BlockingQueuequeue){
this.queue=queue;
}
@Override
publicvoidrun{
this.product;
}
privatevoidproduct{
while(true){
try{
queue.put(count);
System.out.println("出產者正在出產第"+count+"個產品");
count++;
}catch(InterruptedExceptione){
e.printStackTrace;
}
}
}
}
//輸出如下
/**
出產者正在出產第1個產品
出產者正在出產第2個產品
出產者正在出產第3個產品
出產者正在出產第4個產品
出產者正在出產第5個產品
customer正在消費第1個產品===
*/
2.2.3LinkedBlockingQueue
基于鏈表的堵塞行列,內部也保護了一個資料緩沖行列,需求咱們留意的是假如結構一個LinkedBlockingQueue目標,而沒有指定其容量巨細,
LinkedBlockingQueue會默許一個類似無限巨細的容量(Integer.MAX_VALUE),這樣的話,假如出產者的速度一旦大于顧客的速度,也許還沒有比及行列滿堵塞發生,體系記憶體就有或許已被耗費殆盡了,
2.2.4LinkedBlockingQueue和ArrayBlockingQueue的首要差異
ArrayBlockingQueue的初始化必須傳入行列巨細,LinkedBlockingQueue則可以不傳入,
ArrayBlockingQueue用一把鎖操控并發,LinkedBlockingQueue倆把鎖操控并發,鎖的細粒度更細,即前者出產者顧客進出都是一把鎖,后者出產者出產進入是一把鎖,顧客消費是另一把鎖,
ArrayBlockingQueue選用陣列的辦法存取,LinkedBlockingQueue用Node鏈表辦法存取,
2.2.5handler回絕戰略
Java供給了4種丟掉處理的辦法,當然你也可以自己完成,首要是要完成介面:RejectedExecutionHandler中的辦法,
AbortPolicy:不處理,直接拋出反常,
CallerRunsPolicy:只用呼叫者地點執行緒來運轉使命,即提交使命的執行緒,
DiscardOldestPolicy:LRU戰略,丟掉行列里最近最久不運用的一個使命,并履行當時使命,
DiscardPolicy:不處理,丟掉掉,不拋出反常,
2.2.6執行緒池五種狀況
privatestaticfinalintRUNNING=-1<
privatestaticfinalintSHUTDOWN=0<
privatestaticfinalintSTOP=1<
privatestaticfinalintTIDYING=2<
privatestaticfinalintTERMINATED=3<
RUNNING:在這個狀況的執行緒池能判別承受新提交的使命,并且也能處理堵塞行列中的使命,
SHUTDOWN:處于封閉的狀況,該執行緒池不能承受新提交的使命,可是可以處理堵塞行列中現已保存的使命,在執行緒處于RUNNING狀況,呼叫shutdown辦法能切換為該狀況,
STOP:執行緒池處于該狀況時既不能承受新的使命也不能處理堵塞行列中的使命,并且能中止現在執行緒中的使命,當執行緒處于RUNNING和SHUTDOWN狀況,呼叫shutdownNow辦法就可以使執行緒變為該狀況,
TIDYING:在SHUTDOWN狀況下堵塞行列為空,且執行緒中的作業執行緒數量為0就會進入該狀況,當在STOP狀況下時,只要執行緒中的作業執行緒數量為0就會進入該狀況,
TERMINATED:在TIDYING狀況下呼叫terminated辦法就會進入該狀況,可以以為該狀況是最終的停止狀況,
回到執行緒池創立ThreadPoolExecutor,咱們了解了這些引數,再來看看ThreadPoolExecutor的內部作業原理:
判別中心執行緒是否已滿,是進入行列,否:創立執行緒
判別等候行列是否已滿,是:查看執行緒池是否已滿,否:進入等候行列
查看執行緒池是否已滿,是:回絕,否創立執行緒
2.3深入了解ThreadPoolExecutor
進入Execute辦法可以看到:
publicvoidexecute(Runnablecommand){
if(command==null)
thrownewNullPointerException;
intc=ctl.get;
//判別當時活躍執行緒數是否小于corePoolSize,假如小于,則呼叫addWorker創立執行緒履行使命
if(workerCountOf(c)
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失敗,則創立執行緒履行使命,假如這時創立執行緒失敗(當時執行緒數不小于maximumPoolSize時),就會呼叫reject(內部呼叫handler)回絕承受使命,
elseif(!addWorker(command,false))
reject(command);
}
AddWorker辦法:
創立Worker目標,一起也會實體化一個Thread目標,在創立Worker時會呼叫threadFactory來創立一個執行緒,
然后啟動這個執行緒,
2.3.1執行緒池中CTL特點的效果是什么?
看原始碼榜首反響便是這個CTL到底是個什么東東?有啥用?一番研究得出如下定論:
CTL特點包括兩個概念:
privatefinalAtomicIntegerctl=newAtomicInteger(ctlOf(RUNNING,0));
privatestaticintctlOf(intrs,intwc){returnrs|wc;}
runState:即rs標明當時執行緒池的狀況,是否處于Running,Shutdown,Stop,Tidying,
workerCount:即wc標明當時有效的執行緒數,
咱們點擊workerCount即作業狀況記載值,以RUNNING為例,RUNNING=-1<
privatestaticfinalintCOUNT_BITS=Integer.SIZE-3;
既然是29位那么便是Running的值為:
11100000000000000000000000000000
|||
31~29位
那低28位呢,便是記載當時執行緒的總線數啦:
//Packingandunpackingctl
privatestaticintrunStateOf(intc){returnc&~CAPACITY;}
privatestaticintworkerCountOf(intc){returnc&CAPACITY;}
privatestaticintctlOf(intrs,intwc){returnrs|wc;}
從上述代碼可以看到workerCountOf這個函式傳入ctl之后,是經過CTL&CAPACITY操作來獲取當時運轉執行緒總數的,
也便是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位作為狀況值,低28位作為執行緒總數值來進行存盤,
2.3.2shutdownNow和shutdown的差異
看原始碼發現有兩種近乎相同的辦法,shutdownNow和shutdown,規劃者這么規劃天然是有它的道理,那么這兩個辦法的差異在哪呢?
shutdown會把執行緒池的狀況改為SHUTDOWN,而shutdownNow把當時執行緒池狀況改為STOP,
shutdown只會中止一切空閑的執行緒,而shutdownNow會中止一切的執行緒,
shutdown回傳辦法為空,會將當時使命行列中的一切使命履行完畢;而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);
}
}
便是使命在并不只履行創立時指定的firstTask榜首使命,還會從使命行列的中自己主動取使命履行,并且是有或許無時間限制的堵塞等候,以確保執行緒的存活,
默許的是不允許,
2.4CountDownLatch和CyclicBarrier差異
countDownLatch是一個計數器,執行緒完成一個記載一個,計數器遞減,只能只用一次,
CyclicBarrier的計數器更像一個閥門,需求一切執行緒都抵達,然后持續履行,計數器遞加,供給Reset功用,可以多次運用,
3.多執行緒間通訊的幾種辦法
提及多執行緒又不得不提及多執行緒通訊的機制,首要,要短信執行緒間通訊的模型有兩種:共享記憶體和訊息傳遞,以下辦法都是基本這兩種模型來完成的,咱們來基本一道面試常見的標題來分析:
標題:有兩個執行緒A、B,A執行緒向一個集合里面順次增加元素"abc"字串,一共增加十次,當增加到第五次的時分,希望B執行緒可以收到A執行緒的告訴,然后B執行緒履行相關的業務操作,
3.1運用volatile關鍵字
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發出告訴B可以立馬承受并DoSomething,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/168594.html
標籤:Java
上一篇:深入決議ThreadLocal和ThreadLocalMap
下一篇:C#與Java的區別
