1.ThreadLocal和volatile的理解:
ThreadLocal會(huì)為每個(gè)線程中創(chuàng)建一個(gè)變量的副本,每一個(gè)線程持有一個(gè)ThreadLocalMap對(duì)象,ThreadLocalMap的key為ThreadLocal對(duì)象,value為ThreadLocal的變量值。各個(gè)線程管理自己的副本數(shù)據(jù),互相之間不會(huì)有交集。
數(shù)據(jù)結(jié)構(gòu):當(dāng)前線程.ThreadLocalMap<ThreadLocal,value>
常用的使用場(chǎng)景:Session、數(shù)據(jù)庫(kù)連接Connection(每個(gè)線程擁有自己的Connection而不互相干擾)
volatile的兩個(gè)特性:
1)可見性。對(duì)一個(gè)volatile變量的讀,總是能看到(任意線程)對(duì)這個(gè)volatile變量最后的寫入。
2)原子性:對(duì)任意單個(gè)volatile變量的讀/寫具有原子性,但類似于volatile++這種復(fù)合操作不具有原子性。
volatile內(nèi)存語義理解:
1)線程A寫一個(gè)volatile變量,實(shí)質(zhì)上是線程A向接下來將要讀這個(gè)volatile變量的某個(gè)線程發(fā)出了(其對(duì)共享變量所在修改的)消息。
2)線程B讀一個(gè)volatile變量,實(shí)質(zhì)上是線程B接收了之前某個(gè)線程發(fā)出的(在寫這個(gè)volatile變量之前對(duì)共享變量所做修改的)消息。
3)線程A寫一個(gè)volatile變量,隨后線程B讀這個(gè)volatile變量,這個(gè)過程實(shí)質(zhì)上是線程A通過主內(nèi)存向線程B發(fā)送消息。
為實(shí)現(xiàn)上述內(nèi)存語義,以下是基于保守策略的JMM內(nèi)存屏障插入策略:
在每個(gè)volatile寫操作的前面插入一個(gè)StoreStore屏障。
在每個(gè)volatile寫操作的后面插入一個(gè)StoreLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadLoad屏障。
在每個(gè)volatile讀操作的后面插入一個(gè)LoadStore屏障。
注:內(nèi)存屏障是一組處理器指令,用于實(shí)現(xiàn)對(duì)內(nèi)存操作的順序限制,這里用來保證volatile變量可以按照寫-讀的順序執(zhí)行。(編譯器為優(yōu)化代碼會(huì)重排讀寫操作,內(nèi)存屏障確保不會(huì)進(jìn)行重排)。
2.生產(chǎn)-消費(fèi)模式:
要實(shí)現(xiàn)生產(chǎn)消費(fèi)模式,生產(chǎn)者和消費(fèi)者必須可以互相調(diào)用,一般是將二者的實(shí)例放到同一個(gè)類中,二者的實(shí)例在持有中間類。
生產(chǎn)者在沒有數(shù)據(jù)時(shí)進(jìn)行生產(chǎn),生產(chǎn)后通知消費(fèi)者進(jìn)行消費(fèi),還有數(shù)據(jù)時(shí)等待;消費(fèi)者在有數(shù)據(jù)時(shí)進(jìn)行消費(fèi),消費(fèi)完之后通知生產(chǎn)者進(jìn)行生產(chǎn),沒有數(shù)據(jù)時(shí)等待。
關(guān)鍵在于wait和notify的理解,wait和notify在調(diào)用時(shí)對(duì)誰進(jìn)行操作,就要同步誰,即synchronize(誰)。
3.BlockingQueue
實(shí)現(xiàn)主要用于生產(chǎn)者-使用者隊(duì)列,BlockingQueue實(shí)現(xiàn)是線程安全的。所有排隊(duì)方法都可以使用內(nèi)部鎖或其他形式的并發(fā)控制來自動(dòng)達(dá)到它們的目的。
LinkedBlockingQueue(無界的隊(duì)列),ArrayBlockingQueue(有界的隊(duì)列),SynchronousQueue(一種阻塞隊(duì)列,其中每個(gè)插入操作必須等待另一個(gè)線程的對(duì)應(yīng)移除操作 ,反之亦然。同步隊(duì)列沒有任何內(nèi)部容量,甚至連一個(gè)隊(duì)列的容量都沒有,優(yōu)勢(shì)是輕量級(jí),適用于線程間傳遞信號(hào),JDK文檔原話是:它非常適合于傳遞性設(shè)計(jì),在這種設(shè)計(jì)中,在一個(gè)線程中運(yùn)行的對(duì)象要將某些信息、事件或任務(wù)傳遞給在另一個(gè)線程中運(yùn)行的對(duì)象,它就必須與該對(duì)象同步)
ArrayBlockingQueue中,容量滿了之后使用put添加元素會(huì)導(dǎo)致阻塞,直到有容量的時(shí)候繼續(xù)執(zhí)行,take執(zhí)行與之相反的操作;使用add添加元素則會(huì)導(dǎo)致異常,remove執(zhí)行與之相反的操作;使用offer添加元素則可以指定等待可用容量的時(shí)長(zhǎng),如果沒有可用容量,返回false,否則返回true,poll方法執(zhí)行與之相反的操作。put和take是多線程中最常用的組合。
4.PipedReader和PipedWriter
通過輸入輸出在線程間進(jìn)行通信通常很有用。PipedReader的構(gòu)造器中需要一個(gè)PipedWriter實(shí)例。
PipedReader和普通I/O之間的區(qū)別是:PipedReader是可以中斷的,而普通I/O是不可interrupt的
5.CountDownLatch(鎖存儲(chǔ)器)和CyclicBarrier
CountDownLatch被用來同步一個(gè)或多個(gè)任務(wù),強(qiáng)制它們等待由其他任務(wù)執(zhí)行的一組操作完成。CountDownLatch被設(shè)計(jì)為只觸發(fā)一次,計(jì)數(shù)不能被重置。當(dāng)每個(gè)任務(wù)完成時(shí),都會(huì)在這個(gè)所存儲(chǔ)器上調(diào)用countDown(),等待問題解決的任務(wù)在這個(gè)鎖存儲(chǔ)器上調(diào)用await(),將它們自己攔住,直到鎖存儲(chǔ)器計(jì)數(shù)結(jié)束。
CyclicBarrier適用于一組任務(wù)并行執(zhí)行,然后在進(jìn)行下一個(gè)步驟之前等待,直到所有的任務(wù)都完成,才執(zhí)行下一個(gè)動(dòng)作。任務(wù)都調(diào)用CyclicBarrier的await()方法等待同組其他任務(wù)完成,CyclicBarrier的構(gòu)造方法接收一個(gè)或兩個(gè)參數(shù),第一個(gè)為前置任務(wù)數(shù)量,第二個(gè)為要執(zhí)行的后置任務(wù)。
二者不同地方在于,CountDownLatch只能執(zhí)行一次,而CyclicBarrier可以重復(fù)執(zhí)行。
6.DelayQueue 延遲隊(duì)列
延遲隊(duì)列是Delayed元素的一個(gè)無界阻塞隊(duì)列,只有在延遲期滿時(shí)才能從中提取元素
DelayQueue中放置實(shí)現(xiàn)了Delayed接口的對(duì)象,Delayed接口必須實(shí)現(xiàn)compareTo()和getDelay()方法,compare方法決定了DelayQueue.take()出來的Delayed對(duì)象的順序,getDelay()方法必須返回延遲時(shí)間減去當(dāng)前時(shí)間的差值,也就是說每次take()都會(huì)調(diào)用getDelay方法來判斷是否到了延遲時(shí)間,如果getDelay返回的是一個(gè)固定的值,將永遠(yuǎn)不能take出來。
7.PriorityBlockingQueue 優(yōu)先級(jí)隊(duì)列
優(yōu)先級(jí)隊(duì)列允許用戶根據(jù)自己實(shí)現(xiàn)的Compare接口來對(duì)對(duì)象進(jìn)行排序,所以優(yōu)先級(jí)隊(duì)列中的對(duì)象必須實(shí)現(xiàn)Compare接口或者使用第三方Cmpare接口進(jìn)行排序,根據(jù)使用哪個(gè)構(gòu)造方法來決定。與PriorityQueue不同的是,該對(duì)象是線程安全的,是可以被阻塞的。
8.ScheduledThreadPoolException 定時(shí)執(zhí)行器
scheduleAtFixedRate方法與scheduleWithFixedDelay方法的區(qū)別:scheduleAtFixedRate是以固定頻率執(zhí)行任務(wù)(如果任務(wù)執(zhí)行的時(shí)間大于頻率時(shí)長(zhǎng),前一個(gè)任務(wù)執(zhí)行完后立即執(zhí)行下一個(gè)任務(wù),否則,按照固定頻率執(zhí)行);scheduleWithFixedDelay是按照固定延遲時(shí)間執(zhí)行,即前一個(gè)任務(wù)執(zhí)行完后(不管用多長(zhǎng)時(shí)間),都要等待固定的一段時(shí)間再執(zhí)行下一個(gè)任務(wù)。
9.Semaphore 計(jì)數(shù)信號(hào)量
從概念上講,信號(hào)量維護(hù)了一個(gè)許可集(初始化時(shí)要指定大?。T谠S可可用前會(huì)阻塞每一個(gè)acquire(),然后再獲取該許可。每個(gè)release()添加一個(gè)許可,從而可能釋放一個(gè)正在阻塞的獲取者。但是,不使用實(shí)際的許可對(duì)象,Semaphore只對(duì)可用許可的號(hào)碼進(jìn)行計(jì)數(shù),并采取相應(yīng)的行動(dòng)(它只是一個(gè)計(jì)數(shù)器)。
通常,應(yīng)該將用于控制資源訪問的信號(hào)量初始化為公平的(第二個(gè)參數(shù)為true),以確保所有線程都可訪問資源。為其他的種類的同步控制使用信號(hào)量時(shí),非公平排序(第二個(gè)參數(shù)為false)的吞吐量?jī)?yōu)勢(shì)通常要比公平考慮更為重要。
適用場(chǎng)景:連接池、對(duì)象池的設(shè)計(jì)
10.Exchanger 兩個(gè)線程交換數(shù)據(jù)的柵欄
當(dāng)一個(gè)線程到達(dá)exchange調(diào)用點(diǎn)時(shí),如果它的伙伴線程此前已經(jīng)調(diào)用了此方法,那么它的伙伴會(huì)被調(diào)度喚醒并與之進(jìn)行對(duì)象交換,然后各自返回。如果它的伙伴還沒到達(dá)交換點(diǎn),那么當(dāng)前線程將會(huì)被掛起,直至伙伴線程到達(dá)——完成交換正常返回;或者當(dāng)前線程被中斷——拋出中斷異常;又或者是等候超時(shí)——拋出超時(shí)異常。
11.多線程性能調(diào)優(yōu)
盡量使用synchronize關(guān)鍵字,因?yàn)檫@種方法代碼可讀性特別高;在影響到性能的時(shí)候使用Lock對(duì)象,Lock對(duì)象在大量并發(fā)的情況下性能比較穩(wěn)定;只有在性能方面的需求特別高時(shí),考慮使用Atomic,Atomic性能最好,但是使用場(chǎng)景特別有限。
12.免鎖容器
免鎖容器的通用策略:對(duì)容器的修改可以與讀取操作同時(shí)發(fā)生,只要讀取者只能看到修改完的結(jié)果即可,修改是在容器數(shù)據(jù)結(jié)構(gòu)的某個(gè)部分的一個(gè)單獨(dú)的副本上執(zhí)行的,并且這個(gè)副本在修改過程中是不可視的。只有當(dāng)修改完成時(shí),被修改的結(jié)構(gòu)才會(huì)自動(dòng)地與主數(shù)據(jù)結(jié)構(gòu)進(jìn)行交換,之后讀取者就可以看到這個(gè)修改了。
CopyOnWriteArrayList原理:在寫的時(shí)候是先將底層源數(shù)組復(fù)制到新數(shù)組中,然后在新數(shù)組中寫,寫完后更新源數(shù)組。而讀的話只是在源數(shù)組上讀。也就是,讀和寫是分離的。由于寫的時(shí)候每次都要將源數(shù)組復(fù)制到一個(gè)新組數(shù)中,所以寫的效率不高。CopyOnWriteArrayList適合用于讀操作遠(yuǎn)遠(yuǎn)大于寫操作的情景中。
CopyOnWriteArraySet使用CopyOnWriteArrayList實(shí)現(xiàn)其免鎖行為。
ConcurrentHashMap原理:使用鎖分段技術(shù),首先將數(shù)據(jù)分成一段一段的存儲(chǔ),然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個(gè)線程占用鎖訪問其中一個(gè)段數(shù)據(jù)的時(shí)候,其他段的數(shù)據(jù)也能被其他線程訪問。
ConcurrentHashMap是由Segment數(shù)組結(jié)構(gòu)和HashEntry數(shù)組結(jié)構(gòu)組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用于存儲(chǔ)鍵值對(duì)數(shù)據(jù)。它的get方法里將要使用的共享變量都定義成volatile,保證了高效和統(tǒng)一。
獲取ConcurrentHashMap的Size的做法是先嘗試2次通過不鎖住Segment的方式來統(tǒng)計(jì)各個(gè)Segment大小,如果統(tǒng)計(jì)的過程中,容器的count發(fā)生了變化,則再采用加鎖的方式來統(tǒng)計(jì)所有Segment的大?。存i定所有的Segment的put,remove和clean方法,很低效)。
所以,要盡量避免使用ConcurrentHashMap的size方法。
ConcurrentLinkedQueue由head節(jié)點(diǎn)和tair節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)(Node)由節(jié)點(diǎn)元素(item)和指向下一個(gè)節(jié)點(diǎn)的引用(next)組成,節(jié)點(diǎn)與節(jié)點(diǎn)之間就是通過這個(gè)next關(guān)聯(lián)起來,從而組成一張鏈表結(jié)構(gòu)的隊(duì)列。(tair尾節(jié)點(diǎn)并不一定總是指向最后一個(gè)節(jié)點(diǎn),這樣做是為了使用volatile變量的讀操作來減少volatile的寫操作,同樣,并不是每次出隊(duì)時(shí)都更新head節(jié)點(diǎn))?
入隊(duì)過程主要做二件事情。第一是定位出尾節(jié)點(diǎn),第二是使用CAS算法能將入隊(duì)節(jié)點(diǎn)設(shè)置成尾節(jié)點(diǎn)的next節(jié)點(diǎn),如不成功則重試。
出隊(duì)時(shí)首先獲取頭節(jié)點(diǎn)的元素,然后判斷頭節(jié)點(diǎn)元素是否為空,如果為空,表示另外一個(gè)線程已經(jīng)進(jìn)行了一次出隊(duì)操作將該節(jié)點(diǎn)的元素取走,如果不為空,則使用CAS的方式將頭節(jié)點(diǎn)的引用設(shè)置成null,如果CAS成功,則直接返回頭節(jié)點(diǎn)的元素,如果不成功,表示另外一個(gè)線程已經(jīng)進(jìn)行了一次出隊(duì)操作更新了head節(jié)點(diǎn),導(dǎo)致元素發(fā)生了變化,需要重新獲取頭節(jié)點(diǎn)。
13.樂觀鎖和悲觀鎖
獨(dú)占鎖是一種悲觀鎖,synchronized就是一種獨(dú)占鎖,會(huì)導(dǎo)致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。
所謂樂觀鎖就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。
14.CAS 算法
CAS,即 Compare And Swap,樂觀鎖用到的機(jī)制就是CAS算法。
CAS有三個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B,當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相等時(shí),將內(nèi)存值V修改為B,否則什么都不做。
15.ReadWriteLock 讀取鎖
讀-寫鎖允許對(duì)共享數(shù)據(jù)進(jìn)行更高級(jí)別的并發(fā)訪問。雖然一次只有一個(gè)線程(writer線程)可以修改共享數(shù)據(jù),但在許多情況下,任何數(shù)量的線程可以同時(shí)讀取共享數(shù)據(jù)(reader線程),讀-寫鎖利用了這一點(diǎn)。從理論上講,與互斥鎖相比,使用讀-寫鎖所允許的并發(fā)性增強(qiáng)將帶來更大的性能提高。在實(shí)踐中,只有在多處理器上并且只在訪問模式適用于共享數(shù)據(jù)時(shí),才能完全實(shí)現(xiàn)并發(fā)性增強(qiáng)。只有寫操作特別少二讀操作特別多的時(shí)候才使用ReadWriteLock。
16.內(nèi)存一致性屬性
Java?Language Specification 第 17 章定義了內(nèi)存操作(如共享變量的讀寫)的happen-before關(guān)系。只有寫入操作happen-before讀取操作時(shí),才保證一個(gè)線程寫入的結(jié)果對(duì)另一個(gè)線程的讀取是可視的。synchronized和volatile構(gòu)造happen-before關(guān)系,Thread.start()和Thread.join()方法形成happen-before關(guān)系。尤其是:
線程中的每個(gè)操作happen-before稍后按程序順序傳入的該線程中的每個(gè)操作。
一個(gè)解除鎖監(jiān)視器的(synchronized阻塞或方法退出)happen-before相同監(jiān)視器的每個(gè)后續(xù)鎖(synchronized阻塞或方法進(jìn)入)。并且因?yàn)?i>happen-before關(guān)系是可傳遞的,所以解除鎖定之前的線程的所有操作happen-before鎖定該監(jiān)視器的任何線程后續(xù)的所有操作。
寫入volatile字段happen-before每個(gè)后續(xù)讀取相同字段。volatile字段的讀取和寫入與進(jìn)入和退出監(jiān)視器具有相似的內(nèi)存一致性效果,但不需要互斥鎖。
在線程上調(diào)用starthappen-before已啟動(dòng)的線程中的任何線程。
線程中的所有操作happen-before從該線程上的join成功返回的任何其他線程。
17.JMM是如何處理編譯器和處理器對(duì)指令的重排序問題的?
對(duì)于編譯器,JMM的編譯器重排序規(guī)則會(huì)禁止特定類型的編譯器重排序
對(duì)于處理器重排序,JMM的處理器重排序規(guī)則會(huì)要求java編譯器在生成指令序列時(shí),插入特定類型的內(nèi)存屏障(memory barriers,intel稱之為memory fence)指令,通過內(nèi)存屏障指令來禁止特定類型的處理器重排序
總之一句話,JMM是通過禁止特定類型的編譯器重排序和處理器重排序來為程序員提供一致的內(nèi)存可見性保證。