Java面試題-並發篇十六
Java內存模型規定和指引Java程序在不同的內存架構、CPU和操作系統間有確定性地行為。它在多線程的情況下尤其重要。Java內存模型對一個線程所做的變動能被其它線程可見提供了保證,它們之間是先行發生關系。這個關系定義了一些規則讓程序員在並發編程時思路更清晰。比如,先行發生關系確保了: 線程內的代碼能夠按先後順序執行,這被稱為程序次序規則。 對於同一個鎖,一個解鎖操作一定要發生在時間上後發生的另一個鎖定操作之前,也叫做管程鎖定規則。 前一個對volatile的寫操作在後一個volatile的讀操作之前,也叫volatile變量規則。 一個線程內的任何操作必需在這個線程的start()調用之後,也叫作線程啟動規則。 一個線程的所有操作都會在線程終止之前,線程終止規則。 一個對象的終結操作必需在這個對象構造完成之後,也叫對象終結規則。
可傳遞性
更多介紹可以移步並發編程網:
(深入理解java內存模型系列文章:http://ifeve.com/java-memory-model-0)
162,Java中interrupted 和isInterruptedd方法的區別?
interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除而後者不會。Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識為true。當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。 非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態清零。無論如何,一個線程的中斷狀態都有可能被其它線程調用中斷來改變。
163,Java中的同步集合與並發集合有什麽區別?
同步集合與並發集合都為多線程和並發提供了合適的線程安全的集合,不過並發集合的可擴展性更高。在Java1.5之前程序員們只有同步集合來用且在多線程並發的時候會導致爭用,阻礙了系統的擴展性。Java5介紹了並發集合像ConcurrentHashMap,不僅提供線程安全還用鎖分離和內部分區等現代技術提高了可擴展性。 不管是同步集合還是並發集合他們都支持線程安全,他們之間主要的區別體現在性能和可擴展性,還有他們如何實現的線程安全上。 同步HashMap, Hashtable, HashSet, Vector, ArrayList 相比他們並發的實現(ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteHashSet)會慢得多。造成如此慢的主要原因是鎖, 同步集合會把整個Map或List鎖起來,而並發集合不會。並發集合實現線程安全是通過使用先進的和成熟的技術像鎖剝離。 比如ConcurrentHashMap 會把整個Map 劃分成幾個片段,只對相關的幾個片段上鎖,同時允許多線程訪問其他未上鎖的片段。 同樣的,CopyOnWriteArrayList 允許多個線程以非同步的方式讀,當有線程寫的時候它會將整個List復制一個副本給它。 如果在讀多寫少這種對並發集合有利的條件下使用並發集合,這會比使用同步集合更具有可伸縮性。
164,什麽是線程池? 為什麽要使用它?
創建線程要花費昂貴的資源和時間,如果任務來了才創建線程那麽響應時間會變長,而且一個進程能創建的線程數有限。為了避免這些問題,在程序啟動的時候就創建若幹線程來響應處理,它們被稱為線程池,裏面的線程叫工作線程。從JDK1.5開始,Java API提供了Executor框架讓你可以創建不同的線程池。比如單線程池,每次處理一個任務;數目固定的線程池或者是緩存線程池(一個適合很多生存期短的任務的程序的可擴展線程池)
線程池的作用,就是在調用線程的時候初始化一定數量的線程,有線程過來的時候,先檢測初始化的線程還有空的沒有,沒有就再看當前運行中的線程數是不是已經達到了最大數,如果沒有,就新分配一個線程去處理。
就像餐館中吃飯一樣,從裏面叫一個服務員出來;但如果已經達到了最大數,就相當於服務員已經用盡了,那沒得辦法,另外的線程就只有等了,直到有新的“服務員”為止。
線程池的優點就是可以管理線程,有一個高度中樞,這樣程序才不會亂,保證系統不會因為大量的並發而因為資源不足掛掉。
165,Java中活鎖和死鎖有什麽區別?
活鎖:一個線程通常會有會響應其他線程的活動。如果其他線程也會響應另一個線程的活動,那麽就有可能發生活鎖。同死鎖一樣,發生活鎖的線程無法繼續執行。然而線程並沒有阻塞——他們在忙於響應對方無法恢復工作。這就相當於兩個在走廊相遇的人:甲向他自己的左邊靠想讓乙過去,而乙向他的右邊靠想讓甲過去。可見他們阻塞了對方。甲向他的右邊靠,而乙向他的左邊靠,他們還是阻塞了對方。
死鎖:兩個或更多線程阻塞著等待其它處於死鎖狀態的線程所持有的鎖。死鎖通常發生在多個線程同時但以不同的順序請求同一組鎖的時候,死鎖會讓你的程序掛起無法完成任務。
166,如何避免死鎖?
死鎖的發生必須滿足以下四個條件:
互斥條件:一個資源每次只能被一個進程使用。
請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
循環等待條件:若幹進程之間形成一種頭尾相接的循環等待資源關系。
三種用於避免死鎖的技術:
加鎖順序(線程按照一定的順序加鎖)
加鎖時限(線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己占有的鎖)
死鎖檢測
(死鎖原因及如何避免更深理解移步:http://blog.csdn.net/ls5718/article/details/51896159)
167,notify()和notifyAll()有什麽區別?
1,notify()和notifyAll()都是Object對象用於通知處在等待該對象的線程的方法。
2,void notify(): 喚醒一個正在等待該對象的線程。
3,void notifyAll(): 喚醒所有正在等待該對象的線程。
兩者的最大區別在於:
notifyAll使所有原來在該對象上等待被notify的線程統統退出wait的狀態,變成等待該對象上的鎖,一旦該對象被解鎖,他們就會去競爭。
notify他只是選擇一個wait狀態線程進行通知,並使它獲得該對象上的鎖,但不驚動其他同樣在等待被該對象notify的線程們,當第一個線程運行完畢以後釋放對象上的鎖,此時如果該對象沒有再次使用notify語句,即便該對象已經空閑,其他wait狀態等待的線程由於沒有得到該對象的通知,繼續處在wait狀態,直到這個對象發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。
168,什麽是可重入鎖(ReentrantLock)?
Java.util.concurrent.lock 中的 Lock 框架是鎖定的一個抽象,它允許把鎖定的實現作為Java 類,而不是作為語言的特性來實現。這就為Lock 的多種實現留下了空間,各種實現可能有不同的調度算法、性能特性或者鎖定語義。 ReentrantLock 類實現了Lock ,它擁有與synchronized 相同的並發性和內存語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。(換句話說,當許多線程都想訪問共享資源時,JVM可以花更少的時候來調度線程,把更多時間用在執行線程上。)
Reentrant 鎖意味著什麽呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那麽獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放。這模仿了synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續)synchronized塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個synchronized 塊時,才釋放鎖。
169,讀寫鎖可以用於什麽應用場景?
讀寫鎖可以用於 “多讀少寫” 的場景,讀寫鎖支持多個讀操作並發執行,寫操作只能由一個線程來操作
ReadWriteLock對向數據結構相對不頻繁地寫入,但是有多個任務要經常讀取這個數據結構的這類情況進行了優化。ReadWriteLock使得你可以同時有多個讀取者,只要它們都不試圖寫入即可。如果寫鎖已經被其他任務持有,那麽任何讀取者都不能訪問,直至這個寫鎖被釋放為止。
ReadWriteLock 對程序性能的提高主要受制於如下幾個因素:
1,數據被讀取的頻率與被修改的頻率相比較的結果。
2,讀取和寫入的時間
3,有多少線程競爭
4,是否在多處理機器上運行
Java面試題-並發篇十六