多線程(英語(yǔ):multithreading),是指從軟件或者硬件上實(shí)現(xiàn)多個(gè)線程并發(fā)執(zhí)行的技術(shù)。具有多線程能力的計(jì)算機(jī)因有硬件支持而能夠在同一時(shí)間執(zhí)行多于一個(gè)線程,進(jìn)而提升整體處理性能。具有這種能力的系統(tǒng)包括對(duì)稱(chēng)多處理機(jī)、多核心處理器以及芯片級(jí)多處理或同時(shí)多線程處理器。
粗粒度交替多線程
一個(gè)線程持續(xù)運(yùn)行,直到該線程被一個(gè)事件擋住而制造出長(zhǎng)時(shí)間的延遲(可能是內(nèi)存load/store操作,或者程序分支操作)[4]。
舉例來(lái)說(shuō)
周期 i :接收線程 A 的指令 j周期 i+1:接收線程 A 的指令 j+1周期 i+2:接收線程 A 的指令 j+2,而這指令緩存失敗周期 i+3:線程調(diào)度器介入,切換到線程 B周期 i+4:接收線程 B 的指令 k周期 i+5:接收線程 B 的指令 k+1[5]硬件成本
此種多線程硬件支持的目標(biāo),是允許在擋住的線程與已就緒的線程中快速切換。
這些新增功能的硬件有這些優(yōu)勢(shì):
線程切換能夠在一個(gè) CPU 周期內(nèi)完成(實(shí)際上可以沒(méi)有開(kāi)銷(xiāo),上個(gè)周期在運(yùn)行線程A,下個(gè)周期就已在運(yùn)行線程B)。這樣子看起來(lái)像是每個(gè)線程是獨(dú)自運(yùn)行的,沒(méi)有其他線程與目前共享硬件資源。對(duì)操作系統(tǒng)來(lái)說(shuō),通常每個(gè)虛擬線程都被視做一個(gè)處理器。這樣就不需要很大的軟件變更(像是特別寫(xiě)支持多線程的操作系統(tǒng))。為了要在各個(gè)現(xiàn)行中的線程有效率的切換,每個(gè)現(xiàn)行中的線程需要有自己的暫存設(shè)置(register set)。像是為了能在兩個(gè)線程中快速切換,硬件的寄存器需要兩次例示(instantiated)。
細(xì)粒度交替式多線程
執(zhí)行過(guò)程很像桶形處理器(Barrel Processor)就像這樣:
周期 i :接收線程 A 的一個(gè)指令周期 i+1:接收線程 B 的一個(gè)指令周期 i+2:接收線程 C 的一個(gè)指令這種線程的效果是會(huì)將所有從運(yùn)行流水線中的資料從屬(data dependency)關(guān)系移除掉。因?yàn)槊總€(gè)線程是相對(duì)獨(dú)立,流水線中的一個(gè)指令層次結(jié)構(gòu)需要從已跑完流水線中的較舊指令代入輸出的機(jī)會(huì)就相對(duì)的變小了。而在概念上,這種多線程與操作系統(tǒng)的核心先占多任務(wù)(pre-exemptive multitasking)相似。
1.基本概念
進(jìn)程:在操作系統(tǒng)中運(yùn)行的程序就是進(jìn)程,比如:QQ、播放器等。
線程:一個(gè)進(jìn)程可以有多個(gè)線程,如:視頻可以同時(shí)聽(tīng)聲音、看圖像。
并行:多個(gè)cpu實(shí)例或者多臺(tái)機(jī)器同時(shí)執(zhí)行各自的處理邏輯,是真正的同時(shí)。
并發(fā):通過(guò)cpu調(diào)度算法,讓用戶看上去同時(shí)執(zhí)行,實(shí)際上從cpu操作層面不是真正的同時(shí)。
2.線程的生命周期
1)有新建(New)、就緒(Runnable)、運(yùn)行(Running)、阻塞(Blocked)、死亡(Dead)共5種狀態(tài)。
新建狀態(tài):new關(guān)鍵字新建一個(gè)線程后,該線程就處于新建狀態(tài),此時(shí)僅由JVM為其分配內(nèi)存,并初始化成員變量的值。
就緒狀態(tài):線程對(duì)象調(diào)用start()方法之后,該線程處于就緒狀態(tài)。Java虛擬機(jī)會(huì)為其創(chuàng)建方法調(diào)用棧和程序計(jì)數(shù)器,等待調(diào)度運(yùn)行。
運(yùn)行狀態(tài):處于就緒狀態(tài)的線程獲得了CPU,開(kāi)始執(zhí)行run()方法的線程執(zhí)行體,此時(shí)該線程處于運(yùn)行狀態(tài)。
阻塞狀態(tài):處于運(yùn)行狀態(tài)的線程失去所占用資源后,便進(jìn)入阻塞狀態(tài)。
死亡狀態(tài):線程run()方法執(zhí)行結(jié)束后進(jìn)入死亡狀態(tài)。此外,如果線程執(zhí)行了interrupt()或stop()方法,也會(huì)以異常退出的方式進(jìn)入死亡狀態(tài)。
2)線程狀態(tài)的控制:
start():?jiǎn)?dòng)當(dāng)前線程,自動(dòng)調(diào)用當(dāng)前線程的run()方法。
run():通常需要重寫(xiě)Thread類(lèi)中的此方法,將創(chuàng)建的線程要執(zhí)行的操作方法聲明在此方法中。
yield():釋放當(dāng)前CPU的執(zhí)行權(quán)。
join():若線程a中調(diào)用線程b的join(),則線程a進(jìn)入阻塞狀態(tài),直到線程b執(zhí)行完成后線程a才結(jié)束阻塞狀態(tài)。
sleep(long militime):讓線程睡眠指定的毫秒數(shù),指定時(shí)間內(nèi),線程是阻塞狀態(tài),不會(huì)釋放鎖。該方法可以再任何場(chǎng)景下調(diào)用。
wait():執(zhí)行此方法,當(dāng)前線程會(huì)進(jìn)入阻塞狀態(tài),并釋放同步監(jiān)視器(鎖)。該方法必須在同步代碼塊和同步方法中才能調(diào)用。
notify():?jiǎn)拘驯粀ait的一個(gè)線程,多個(gè)線程wait時(shí),喚醒優(yōu)先度最高的。
notifyAll():?jiǎn)拘阉斜粀ait的線程。
LockSupport():LockSupport.park()和LockSupport.unpark()實(shí)現(xiàn)線程的阻塞和喚醒。
3.多線程的5種創(chuàng)建方式
① 繼承Thread類(lèi),重寫(xiě)run()方法。
② 實(shí)現(xiàn)Runnable接口,重寫(xiě)run()方法。
③ 匿名內(nèi)部類(lèi)的方式,重寫(xiě)run()方法。相當(dāng)于繼承了Thread類(lèi)。new Thread(){ public void run(){邏輯功能} }.start();
④ Lambda表達(dá)式創(chuàng)建,相當(dāng)于實(shí)現(xiàn)Runnable接口的方法。new Thread(()->{邏輯功能}).start();
⑤ 線程池創(chuàng)建。Executor pool = Executors.newFixedThreadPool(); pool.excute(new Runnable(){public void run(){邏輯功能}});
線程池創(chuàng)建的一些參數(shù):
corePoolSize:隊(duì)列沒(méi)滿時(shí),線程最大并發(fā)數(shù)。
maximumPoolSizes:隊(duì)列滿后線程能夠達(dá)到的最大并發(fā)數(shù)。
keepAliveTime:空閑線程過(guò)多久被回收的時(shí)間限制。
unit:keepAliveTime的時(shí)間單位。
workQueue:阻塞的隊(duì)列類(lèi)型。
RejectedExecutionHandler:超出maximumPoolSizes + workQueue時(shí),任務(wù)會(huì)交給RejectedExecutionHandler來(lái)處理。
4.線程的同步
為了防止多個(gè)線程訪問(wèn)一個(gè)數(shù)據(jù)對(duì)象時(shí),對(duì)數(shù)據(jù)造成破壞,采用線程同步來(lái)保證多線程安全訪問(wèn)競(jìng)爭(zhēng)資源。
1)普通同步方法:synchronized關(guān)鍵字加在普通方法上,此時(shí)鎖就是當(dāng)前實(shí)例對(duì)象,進(jìn)入同步方法前要獲取當(dāng)前實(shí)例的鎖。
2)靜態(tài)同步方法:synchronized關(guān)鍵字加在靜態(tài)方法上,此時(shí)鎖就是當(dāng)前類(lèi)的class對(duì)象,進(jìn)入同步方法前要獲取當(dāng)前類(lèi)對(duì)象的鎖。
3)同步方法塊:synchronized關(guān)鍵字加在代碼塊前,小括號(hào)中指定鎖是什么,進(jìn)入同步代碼塊前就需要獲取指定的鎖。
synchronized底層實(shí)現(xiàn):
數(shù)據(jù)在JVM內(nèi)存的存儲(chǔ):Java對(duì)象頭、moniter對(duì)象監(jiān)視器。
① 在JVM虛擬機(jī)中,對(duì)象在內(nèi)存中的存儲(chǔ)布局分為三個(gè)區(qū)域:對(duì)象頭(Header)、實(shí)例數(shù)據(jù)(Instance Data)、對(duì)齊填充(Padding)。Java對(duì)象頭包括:類(lèi)型指針(Klass Pointer)和標(biāo)記字段(Mark Word)。類(lèi)型指針是對(duì)象只想它的類(lèi)元數(shù)據(jù)的指針,虛擬機(jī)通過(guò)這個(gè)指針來(lái)確定該對(duì)象是哪個(gè)類(lèi)的實(shí)例。標(biāo)記字段用于存儲(chǔ)對(duì)象自身的運(yùn)行時(shí)數(shù)據(jù),比如哈希碼、鎖狀態(tài)標(biāo)志、線程持有的鎖等。所以,synchronized使用的鎖對(duì)象是存儲(chǔ)在Java對(duì)象頭里的標(biāo)記字段里。
② moniter:對(duì)象監(jiān)視器可以類(lèi)比為一個(gè)特殊的房間,房間中有一些被保護(hù)的數(shù)據(jù),monitor保證每次只有一個(gè)線程能進(jìn)入房間,進(jìn)入即為持有monitor,退出即為釋放monitor。使用synchronized加鎖的同步代碼塊在字節(jié)碼引擎中執(zhí)行時(shí),主要就是通過(guò)鎖對(duì)象monitor的取用(monitorenter)與釋放(monitorexit)來(lái)實(shí)現(xiàn)的。
5.多線程引入問(wèn)題
1)線程安全問(wèn)題
① 原子性:常通過(guò)synchronized或者ReentrantLock來(lái)保證原子性。
② 可見(jiàn)性:指一個(gè)線程修改了某個(gè)變量值,其他線程能夠立即得到這個(gè)修改的值。每個(gè)線程都有自己的工作內(nèi)存,工作內(nèi)存和主存間要通過(guò)store和load進(jìn)行交互??梢?jiàn)性問(wèn)題常使用volatile關(guān)鍵字解決。當(dāng)一個(gè)共享變量被volatile修飾時(shí),它會(huì)保證修改的值立即更新到主存,當(dāng)其他線程需要讀取時(shí),會(huì)去主存中讀取新值,而普通共享變量不能保證可見(jiàn)性,因?yàn)槠浔恍薷暮笏⑿禄刂鞔娴臅r(shí)間是不確定的。
2)線程死鎖
由于兩個(gè)或多個(gè)線程互相持有對(duì)方所需的資源,導(dǎo)致線程都處于等待狀態(tài),無(wú)法繼續(xù)執(zhí)行。
3)上下文切換
多線程有線程創(chuàng)建和線程上下文切換的開(kāi)銷(xiāo)。CPU通常會(huì)給不同的線程分配時(shí)間片,當(dāng)CPU從一個(gè)線程切換到另外一個(gè)線程的時(shí)候,CPU需要保存當(dāng)前線程的本地?cái)?shù)據(jù),程序指針等狀態(tài),并加載下一個(gè)要執(zhí)行的線程的本地?cái)?shù)據(jù)、程序指針等,這個(gè)切換就稱(chēng)為上下文切換。通常使用無(wú)鎖并發(fā)編程、CAS算法、協(xié)程等方式解決。
6.使用ReentrantLock
Java語(yǔ)言直接提供了synchronized關(guān)鍵字用于加鎖,但這種鎖存在兩個(gè)問(wèn)題:①很重,②獲取時(shí)必須一直等待,沒(méi)有額外的嘗試機(jī)制。java.util.concurrent.locks包提供的ReentrantLock用于替代synchronized加鎖。調(diào)用ReentrantLock()對(duì)象的lock()方法獲取鎖,最后調(diào)用unlock()方法手動(dòng)釋放鎖。ReentrantLock是可重入鎖,和synchronized一樣,一個(gè)線程可以多次獲取同一個(gè)鎖。和synchronized不同的是,ReentrantLock可以嘗試獲取鎖,即:調(diào)用ReentrantLock()對(duì)象的tryLock()方法,其中傳入等待時(shí)間,時(shí)間單位,如果在這個(gè)時(shí)間后仍然沒(méi)有獲取到鎖,tryLock()方法會(huì)返回false,程序可以去做其他事情。
synchronized可以配合wait()和notify()實(shí)現(xiàn)線程在條件不滿足時(shí)等待,條件滿足時(shí)喚醒,而ReentrantLock則需要借助Condition對(duì)象來(lái)實(shí)現(xiàn),注意Condition對(duì)象必須來(lái)自于ReentrantLock()對(duì)象調(diào)用newCondition()方法,這樣才能獲得一個(gè)綁定了ReentrantLock實(shí)例的Condition實(shí)例。Condition提供了await()、signal()、signalAll()方法,與wait()、notify()、notifyAll()是一致的。
① await():會(huì)釋放當(dāng)前鎖,進(jìn)入等待狀態(tài);
② signal():會(huì)喚醒某個(gè)等待線程;
③ signalAll():會(huì)喚醒所有等待線程;
此外,和tryLock()類(lèi)似,await()可以在等待指定時(shí)間后,如果還沒(méi)有被其他線程通過(guò)signal()或signalAll()喚醒,可以自己醒來(lái):
if ( condition.await(1, TimeUnit.SECOND) ) {
// 被其他線程喚醒
} else {
// 指定時(shí)間內(nèi)沒(méi)有被其他線程喚醒
}
7.使用ReadWriteLock
ReentrantLock保證了只有一個(gè)線程可以執(zhí)行臨界區(qū)代碼,但是有些時(shí)候,這種保護(hù)有點(diǎn)過(guò)頭。有些方法只是讀取數(shù)據(jù),并不修改數(shù)據(jù),此時(shí)應(yīng)該允許多個(gè)線程同時(shí)調(diào)用才對(duì)。使用ReadWriteLock可以解決這個(gè)問(wèn)題,它可以保證:
① 只允許一個(gè)線程寫(xiě)入(此時(shí)其他線程既不能寫(xiě)入也不能讀取);
② 沒(méi)有寫(xiě)入時(shí),多個(gè)線程允許同時(shí)讀(提高性能);
使用方法:new出來(lái)ReadWriteLock()對(duì)象實(shí)例后,調(diào)用該對(duì)象的readLock()和writeLock()分別獲取讀鎖和寫(xiě)鎖實(shí)例,接著在讀方法中使用讀鎖,寫(xiě)方法中使用寫(xiě)鎖。
private final ReadWriteLock rwlock = new ReentrantReadWriteLock();
private final Lock rlock = rwlock.readLock();
private final Lock wlock = rwlock.writeLock();
8.使用StampedLock
ReadWriteLock讀的過(guò)程中不允許寫(xiě),是一種悲觀的讀鎖。其實(shí)讀的過(guò)程中大概率不會(huì)有寫(xiě)操作的發(fā)生,所以并發(fā)效率有待提高。StampedLock和ReadWriteLock相比,讀的過(guò)程中也允許獲取寫(xiě)鎖后寫(xiě)入。這樣一來(lái),我們讀的數(shù)據(jù)就可能不一致,所以,需要一點(diǎn)額外的代碼來(lái)判斷讀的過(guò)程中是否有寫(xiě)入,這種讀鎖是一種樂(lè)觀鎖。StampedLock是不可重入鎖,不能在一個(gè)線程中反復(fù)獲取同一個(gè)鎖。其提供了將悲觀讀鎖升級(jí)為寫(xiě)鎖的功能,它主要使用在if-then-update的場(chǎng)景:即先讀,如果讀的數(shù)據(jù)滿足條件,就返回,如果讀的數(shù)據(jù)不滿足條件,再嘗試寫(xiě)。
使用方法:new出來(lái)StampedLock()對(duì)象實(shí)例后,調(diào)用該對(duì)象的readLock()和writeLock()可以分別獲取讀鎖和寫(xiě)鎖實(shí)例并上鎖,同時(shí)還會(huì)返回版本號(hào),釋放的時(shí)候調(diào)用unclockWrite()或者unlockRead()方法需要傳入版本號(hào)。調(diào)用tryOptimisticRead()獲得樂(lè)觀讀鎖,同時(shí)返回版本號(hào),它在操作數(shù)據(jù)前并沒(méi)有通過(guò) CAS設(shè)置鎖的狀態(tài),僅僅通過(guò)位運(yùn)算測(cè)試,所以不需要顯式地釋放鎖。通常獲取樂(lè)觀鎖,讀入數(shù)據(jù)后,會(huì)調(diào)用validate()方法傳入版本號(hào)stamp進(jìn)行驗(yàn)證,如果中途有寫(xiě)入,則版本號(hào)會(huì)發(fā)生變化,方法會(huì)返回false,此時(shí)需要通過(guò)獲得悲觀鎖再重新讀入數(shù)據(jù)。
9.使用Semaphore
上面的鎖保證同一時(shí)刻只有一個(gè)線程能訪問(wèn)(ReentrantLock),或者只有一個(gè)線程能寫(xiě)入(ReadWriteLock)。還有一種受限資源,需要保證同一時(shí)刻最多有N個(gè)線程能訪問(wèn),比如同一時(shí)刻最多創(chuàng)建100個(gè)數(shù)據(jù)庫(kù)連接,最多允許10個(gè)用戶下載等。這種限制數(shù)量的鎖,用Lock數(shù)組來(lái)實(shí)現(xiàn)很麻煩,此時(shí)就可以使用可以使用Semaphore。其本質(zhì)上就是一個(gè)信號(hào)計(jì)數(shù)器,用于限制同一時(shí)間的最大訪問(wèn)數(shù)量。
使用方法:new出來(lái)Semaphore()對(duì)象實(shí)例,其中傳入允許訪問(wèn)的線程數(shù)量,在需要控制的方法中,調(diào)用acquire()方法,接著完成功能邏輯,最后調(diào)用release()方法釋放。調(diào)用acquire()可能會(huì)進(jìn)入等待,直到滿足條件為止。也可以使用tryAcquire()指定等待時(shí)間。
10.使用Future
Runnable接口有個(gè)問(wèn)題,它的方法沒(méi)有返回值。如果任務(wù)需要一個(gè)返回結(jié)果,那么只能保存到變量,還要提供額外的方法讀取,非常不方便。Callable接口和Runnable接口比,多了一個(gè)返回值功能,并且Callable接口是一個(gè)泛型接口,可以返回指定結(jié)果的類(lèi)型。線程池對(duì)象的submit()方法提交任務(wù)執(zhí)行后會(huì)返回一個(gè)Future對(duì)象,也支持泛型,其表示一個(gè)未來(lái)能獲得結(jié)果的對(duì)象。當(dāng)我們提交一個(gè)Callable任務(wù)后,我們會(huì)同時(shí)獲得一個(gè)Future對(duì)象,然后,我們?cè)谥骶€程某個(gè)時(shí)刻調(diào)用Future對(duì)象的get()方法,就可以獲得異步執(zhí)行的結(jié)果。在調(diào)用get()時(shí),如果異步任務(wù)已經(jīng)完成,我們就直接獲得結(jié)果。如果異步任務(wù)還沒(méi)有完成,那么get()會(huì)阻塞,直到任務(wù)完成后才返回結(jié)果。
Future接口定義的方法有:
get():獲取結(jié)果(可能會(huì)等待);
get(long timeout, TimeUnit unit):獲取結(jié)果,但只等待指定的時(shí)間;
cancel(boolean mayInterruptIfRunning):取消當(dāng)前任務(wù);
isDone():判斷任務(wù)是否已完成。
11.使用CompletableFuture
使用Future獲得異步執(zhí)行結(jié)果時(shí),要么調(diào)用阻塞方法get(),要么輪詢看isDone()是否為true,這兩種方法都不是很好,因?yàn)橹骶€程也會(huì)被迫等待。CompletableFuture針對(duì)Future做了改進(jìn),可以傳入回調(diào)對(duì)象,當(dāng)異步任務(wù)完成或者發(fā)生異常時(shí),自動(dòng)調(diào)用回調(diào)對(duì)象的回調(diào)方法。
使用方式:調(diào)用CompletableFuture的supplyAsync()創(chuàng)建CompletableFuture對(duì)象,其中傳入實(shí)現(xiàn)了Supplier接口的對(duì)象(無(wú)傳入值,有返回值),他會(huì)被提交給默認(rèn)的線程執(zhí)行。調(diào)用thenAccept()方法,其接收實(shí)現(xiàn)了Consumer接口的對(duì)象(有傳入值,無(wú)返回值),設(shè)置執(zhí)行完成時(shí)的回調(diào)方法。調(diào)用exceptionally()方法,接收實(shí)現(xiàn)了Function接口的對(duì)象,設(shè)置報(bào)異常時(shí)的回調(diào)方法。
12.使用ForkJoin
Java 7開(kāi)始引入了一種新的Fork/Join線程池,它可以執(zhí)行一種特殊的任務(wù):把一個(gè)大任務(wù)拆成多個(gè)小任務(wù)并行執(zhí)行。
比如:計(jì)算一個(gè)超大數(shù)組的和,可以把數(shù)組拆成兩部分,分別計(jì)算,最后加起來(lái)就是最終結(jié)果,這樣可以用兩個(gè)線程并行執(zhí)行,如果拆成兩部分還是很大,我們還可以繼續(xù)拆,用4個(gè)線程并行執(zhí)行。這就是Fork/Join任務(wù)的原理:判斷一個(gè)任務(wù)是否足夠小,如果是,直接計(jì)算,否則,就分拆成幾個(gè)小任務(wù)分別計(jì)算。這個(gè)過(guò)程可以反復(fù)“裂變”成一系列小任務(wù)。
使用方法:新建類(lèi)繼承RecursiveTask,其中重寫(xiě)compute方法,設(shè)定閾值,如果任務(wù)小于設(shè)定的閾值,就直接計(jì)算,最后返回結(jié)果。如果任務(wù)大于設(shè)定的閾值,就分裂成兩個(gè)小任務(wù),調(diào)用invokeAll()傳入兩個(gè)小任務(wù),再分別調(diào)用兩個(gè)小任務(wù)的join()方法來(lái)得到返回結(jié)果,最后將兩個(gè)結(jié)果加起來(lái)返回。
13. 使用ThreadLocal
上下文(Context):在一個(gè)線程中,橫跨若干方法調(diào)用,需要傳遞的對(duì)象,通常稱(chēng)之為上下文(Context),它是一種狀態(tài),可以是用戶身份、任務(wù)信息等。
Web應(yīng)用程序是典型的多任務(wù)應(yīng)用,每個(gè)用戶請(qǐng)求頁(yè)面時(shí),我們都會(huì)創(chuàng)建一個(gè)任務(wù),去完成類(lèi)似以下的工作:檢查權(quán)限、做工作、保存狀態(tài)、發(fā)送響應(yīng)。如果這些工作中也需要用到上下文(context),此處就是user實(shí)例,可以簡(jiǎn)單地直接通過(guò)參數(shù)傳入,但是往往一個(gè)方法又會(huì)調(diào)用其他很多方法,這樣會(huì)導(dǎo)致User傳遞到所有地方,但是給每個(gè)方法都增加一個(gè)Context參數(shù)非常麻煩,而且如果調(diào)用鏈中有無(wú)法修改源碼的第三方庫(kù),context就傳不進(jìn)去了。ThreadLocal就很適合解決這個(gè)問(wèn)題,它可以在一個(gè)線程中傳遞同一個(gè)對(duì)象。
public void process(User user) {
checkPermission();
doWork();
saveStatus();
sendResponse();
}
使用方法:ThreadLocal實(shí)例通??偸且造o態(tài)字段初始化,通過(guò)設(shè)置一個(gè)User實(shí)例關(guān)聯(lián)到ThreadLocal中,在移除之前,所有方法都可以隨時(shí)獲取到該User實(shí)例。普通的方法調(diào)用一定是同一個(gè)線程執(zhí)行的,所以,該線程中所有方法調(diào)用threadLocalUser.get()獲取的User對(duì)象是同一個(gè)實(shí)例。ThreadLocal相當(dāng)于給每個(gè)線程都開(kāi)辟了一個(gè)獨(dú)立的存儲(chǔ)空間,各個(gè)線程的ThreadLocal關(guān)聯(lián)的實(shí)例互不干擾,特別注意ThreadLocal一定要在finally中清除。因?yàn)楫?dāng)前線程執(zhí)行完相關(guān)代碼后,很可能會(huì)被重新放入線程池中,如果ThreadLocal沒(méi)有被清除,該線程執(zhí)行其他代碼時(shí),會(huì)把上一次的狀態(tài)帶進(jìn)去。其實(shí),可以ThreadLocal看成一個(gè)全局Map:每個(gè)線程獲取ThreadLocal變量時(shí),總是使用Thread自身作為key。