1. 程式人生 > >Java面試準備之多線程

Java面試準備之多線程

adl 強行 aio 刪除 多條 sum notify callable 修改

什麽叫線程安全?舉例說明

多個線程訪問某個類時,不管運行時環境采用何種調度方式或者這些線程將如何交替執行,並且在主調代碼中不需要任何額外的同步或者協同,這個類都能表現出正確的行為,那麽就稱這個類是線程安全的。 比如無狀態對象一定是線程安全的。

進程和線程的區別

調度: 線程是調度的基本單位,進程是擁有資源的基本單位。同一進程的中線程的切換不會引起進程的切換,不同進程中進行線程切換會引起進程的切換。 擁有資源:進程是擁有資源的基本單位,線程除了自身的棧外一般不擁有資源。而是和其他線程共享同一進程中的資源。 系統開銷:由於創建進程或者撤銷進程時,系統都要分配和回收資源,如內存空間,I/O設備等,操作系統所付出的開銷遠大於創建或撤銷進程時的開銷。

volatile的理解

Volatile自身特性: Volatile 是輕量級的synchronized,它在多處理器開發過程中保證了共享變量的“可見性”,可見性是指當一個線程的某個共享變量發生改變時,另一個線程能夠讀取到這個修改的值。Voaltile變量修飾的變量在進行寫操作時在多核處理器下首先將當前處理器緩存行的數據寫回到系統內存中。為了保證一致性,其他處理器嗅探到總線上傳播的數據,發現數據被修改了自己緩存地址的數據無效。 Volatile 可以禁止重排序, Volatile 能保持單個簡單volatile變量的讀/寫操作的具有原子性。但不能保證自增自減的原子性。

內存語義來講: volatile變量的寫-讀與鎖的釋放-獲取具有相同語義,volatile的寫與鎖的釋放有相同的內存語義,volatile讀與鎖的獲取具有相同語義。 線程A寫一個volatile變量,實質上是線程A向接下來要讀這個volatile變量的某個線程發出消息 線程B讀一個volatile變量,實質上是線程B接收了之前某個線程發出的消息。 線程A寫volatile變量,隨後線程B讀這個變量,這個過程實質上線程A通過內存向B發送消息。 內存語義的實現,也是禁止重排序特性: 為了實現volatile內存語義,JMM限制了對volatile重排序做了限制: 1.當第二個操作是volatile寫時,不管第一個操作時什麽,都不能重排序。 2.當第一個操作是volatile讀時,不管第二個操作是什麽,都不能重排序。 3.當第一個操作是volatile寫,第二個操作是volatile讀時,不重排序。 為了實現volatile的內存語義,編譯器生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序。JMM采取保守策略。

  1. 在每個volatile寫操作前面插入一個StoreStore屏障
  2. 在每個volatile寫操作後面插入一個StoreLoad屏障
  3. 在每個volatile讀操作後面插入一個LoadLoad屏障
  4. 在每個volatile讀操作後面插入一個LoadStore屏障

原子性實現機制

處理器提供總線鎖定和緩存鎖定兩種方式來保證復雜內存操作的原子性。 總線型:就是使用處理器提供一個LOCK信號,當一個處理器在總線傳輸信號時,其他處理器的請求將被阻塞住,那麽該處理獨占內存。所以總線鎖定開銷大。 緩存鎖定:內存區域如果被緩存在緩存行中,且在在lock期間被鎖定,當它執行鎖操作寫回內存時,處理器總線不在鎖定而是通過修改內部的內存地址並使用緩存一致性制阻止同時修改保證操作的原子性。緩存一致性進制兩個以上的處理器同時修改內存區域數據,其他處理器回寫被鎖定並且使其緩存行無效。

Java原子性操作實現原理

使用循環CAS實現原子性操作,CAS是在操作期間先比較舊值,如果舊值沒有發生改變,才交換成新值,發生了變化則不交換。這種方式會產生以下幾種問題:1.ABA問題,通過加版本號解決;2.循環時間過長開銷大,一般采用自旋方式實現;3. 只能保證一個共享變量的原子操作。

Java內存模型

Java內存模型控制線程之間的通信,決定了一個線程對共享變量的寫入何時對另一個線程可見。它屬於語言及的內存模型,它確保在不同編譯器和不同的處理平臺之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程序員提供一致的內存可見性保證。JMM的核心目標是找到一個好的平衡點,一方面是為程序員提供足夠強的內存可見性保證(提供happens-before規則),另一方面對編譯器和處理器的限制盡可能地放松(只要不改變程序結果,怎麽優化都可以)

可見性保證 為了提供內存可見性保證,JMM向程序員保證了以下hapens-before規則: 1.程序順序規則:一個線程的每個操作happen-before與該線程的任意後續操作。 2.監視器鎖規則:一個鎖的解鎖,happens-before於隨後這個鎖的加鎖。 3.Volatile變量規則:對一個volatile域的寫,happens-before於任意後續這個域的讀。 4.傳遞性, 如果A happens-before B, 且B happens-before C 那麽A happens-before C 5.線程啟動規則:如果線程A執行操作ThreadB.start().那麽線程A中的任意操作happens-before與線程B中的任意操作。 6.線程結束規則: 線程中的任何操作都必須在其線程檢測到該線程已經結束之前執行,或者從Thread.join中成功返回,或者在調用Thread.isAlive時返回false. 7.中斷規則:當一個線程在另一個線程上調用interrupt時,必須在被中斷線程檢測到interrupt調用之前執行(通過拋出InterruptedException,或者調用isInterrupted和interrupted) 8.終結器規則: 對象的構造函數必須在啟動該對象的終結器之前執行完成。

禁止重排序 為了保證內存可見性,java編輯器在生成指令序列的適當位置插入內存屏障指令來禁止特定類型的處理器重排序。 重排序:編譯器和處理器為了優化程序性能對指令進行重新排序的一種手段。 1)編譯器優化的重排序: 編譯器在不改變單線程程序語義的前提下可以重新安排語句順序。 2)指令級並行的重排序.現代處理器采用指令級並行技術來將多條指令重疊執行。如果不存在數據依賴性,處理器可以改變對應指令的執行順序。 3)內存系統重排序。由於處理器使用緩存和讀/寫緩沖區,這使得加載和存儲操作看上去可能在亂序執行。

Final域的內存語義

對於final域編譯器和處理器要遵守兩個重排序規則: 1.在構造器函數內對final域的寫入,與隨後把這個被構造對象的引用賦值給一個引用變量,這兩個操作之間不能重排序。(保證了對象引用為任何線程可見之前,對象的final域已經被正確初始化過) 2.初次讀一個包含final域的對象引用,與隨後初次讀這個final域這兩個操作不能重排序。 為何保證其內存語義:可以為java程序員提供安全保證,只要對象是正確構造的,那麽不需要使用同步就可以保證線程都能看到這個fianal域在構造函數中被初始化之後的值。

避免死鎖的常見方法:

1)不免一個線程同時獲取多個鎖 2)避免一個線程在鎖內同時占用多個資源,盡量保證每個鎖只占用一個資源 3)嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制。 4)對數據庫鎖,加鎖和解鎖必須在一個數據庫連接裏,否則會出現解鎖失敗的情況。

死鎖的必要條件?怎麽克服?

答:產生死鎖的四個必要條件: 互斥條件:一個資源每次只能被一個進程使用。 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。 循環等待條件:若幹進程之間形成一種頭尾相接的循環等待資源關系。 這四個條件是死鎖的必要條件,只要系統發生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會發生死鎖。

死鎖的解決方法: a 撤消陷於死鎖的全部進程; b 逐個撤消陷於死鎖的進程,直到死鎖不存在; c 從陷於死鎖的進程中逐個強迫放棄所占用的資源,直至死鎖消失。 d 從另外一些進程那裏強行剝奪足夠數量的資源分配給死鎖進程,以解除死鎖狀態

CountDownLatch(閉鎖) 與CyclicBarrier(柵欄)的區別

CountDownLatch: 允許一個或多個線程等待其他線程完成操作. CyclicBarrier:阻塞一組線程直到某個事件發生。

  1. 閉鎖用於等待事件、柵欄是等待線程.
  2. 閉鎖CountDownLatch做減計數,而柵欄CyclicBarrier則是加計數。
  3. CountDownLatch是一次性的,CyclicBarrier可以重用。
  4. CountDownLatch一個線程(或者多個),等待另外N個線程完成某個事情之後才能執行。CyclicBarrier是N個線程相互等待,任何一個線程完成之前,所有的線程都必須等待。 CountDownLatch 是計數器, 線程完成一個就記一個,就像報數一樣, 只不過是遞減的. 而CyclicBarrier更像一個水閘, 線程執行就像水流, 在水閘處都會堵住, 等到水滿(線程到齊)了, 才開始泄流.

execute 和submit的區別

Execute()用於提交不需要返回值得任務,submit()用於提交需要返回值的任務,發揮Future類型的對象。

Shutdown和shutdownNow的區別

它們的原理都是遍歷線程池中的工作線程,然後逐個調用線程的Internet方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。 ShutdownNow首先將線程池的狀態設置成STOP,然後嘗試停止所有正在執行或暫停的任務,並返回等待執行任務的列表。而shutdown只是將線程池設置成SHUTDOWN狀態,然後中斷沒有正在執行任務的線程。

ThreadLocal的設計理念與作用。

ThreadLocal並不是一個Thread,而是Thread的局部變量,當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本

http://blog.csdn.net/lufeng20/article/details/24314381

同步

同步就是協同步調,按預定的先後次序進行運行。

sleep() 和 wait() 區別

答:sleep()方法是線程類(Thread)的靜態方法,導致此線程暫停執行指定時間,將執行機會給其他線程,但是監控狀態依然保持,到時後會自動恢復(線程回到就緒(ready)狀態),因為調用 sleep 不會釋放對象鎖。wait() 是 Object 類的方法,對此對象調用 wait()方法導致本線程放棄對象鎖(線程暫停執行),進入等待此對象的等待鎖定池,只有針對此對象發出 notify 方法(或 notifyAll)後本線程才進入對象鎖定池準備獲得對象鎖進入就緒狀態。

sleep() 和 yield() 區別

① sleep() 方法給其他線程運行機會時不考慮線程的優先級,因此會給低優先級的線程以運行的機會;yield() 方法只會給相同優先級或更高優先級的線程以運行的機會; ② 線程執行 sleep() 方法後轉入阻塞(blocked)狀態,而執行 yield() 方法後轉入就緒(ready)狀態; ③ sleep() 方法聲明拋出InterruptedException,而 yield() 方法沒有聲明任何異常; ④ sleep() 方法比 yield() 方法(跟操作系統相關)具有更好的可移植性。

線程同步相關的方法。

wait():使一個線程處於等待(阻塞)狀態,並且釋放所持有的對象的鎖; sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要捕捉InterruptedException 異常; notify():喚醒一個處於等待狀態的線程,當然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且與優先級無關; notityAll():喚醒所有處入等待狀態的線程,註意並不是給所有喚醒線程一個對象的鎖,而是讓它們競爭;

什麽是線程池(thread pool)

答:在面向對象編程中,創建和銷毀對象是很費時間的,因為創建一個對象要獲取內存資源或者其它更多資源。在 Java 中更是如此,虛擬機將試圖跟蹤每一個對象,以便能夠在對象銷毀後進行垃圾回收。所以提高服務程序效率的一個手段就是盡可能減少創建和銷毀對象的次數,特別是一些很耗資源的對象創建和銷毀,這就是"池化資源"技術產生的原因。線程池顧名思義就是事先創建若幹個可執行的線程放入一個池(容器)中,需要的時候從池中獲取線程不用自行創建,使用完畢不需要銷毀線程而是放回池中,從而減少創建和銷毀線程對象的開銷。

http://www.cnblogs.com/dolphin0520/p/3932921.html

ConcurrentHashMap實現原理

ConcurrentHashMap和Hashtable主要區別就是圍繞著鎖的粒度以及如何鎖。 Hashtabl在競爭激烈的環境下表現效率低下的原因是一把鎖鎖住整張表,導致所有線程同時競爭一個鎖。ConcurrentHashMap采用分段鎖,每把鎖鎖住容器中的一個Segment。那麽多線程訪問容器裏不同的Segment的數據時線程就不會存在競爭,從而有效提高並發訪問效率。首先是將數據分層多個Segment存儲,並為每個Segment分配一把鎖,當一個線程範圍其中一段數據時,其他線程可以訪問其他段的數據。

數據結構: ConcurrentHashMap內部是有Segment數組和HashEntry數組組成。一個ConcurrentHashMapMap裏包含一個Segment數組,而Segment的結構和HashMap一樣,裏面是由一個數組和鏈表結構組成,所以一個Segment內部包含一個HashEntry數組。每個HashEntry是一個鏈表結構,對於HashEntry數組進行修改時首先需要獲取與它對應的Segment鎖。默認情況下有16個Segment

Segment的定位: 使用Wang/Jenkins hash變種算法對元素的hashCode進行一次再散列,目的是為了減少散列沖突。

ConcurrentHashMap的操作: 1.) get get操作實現非常簡單高效。先經過一次在散列,然後用這個散列值通過散列運算定位到Segment,再通過散列算法定位到元素。get之所以高效是因為整個get過程不需要加鎖,除非讀到空值才會加鎖重讀。實現該技術的技術保證是保證HashEntry是不可變的。 第一步是訪問count變量,這是一個volatile變量,由於所有的修改操作在進行結構修改時都會在最後一步寫count 變量,通過這種機制保證get操作能夠得到幾乎最新的結構更新。對於非結構更新,也就是結點值的改變,由於HashEntry的value變量是 volatile的,也能保證讀取到最新的值。接下來就是根據hash和key對hash鏈進行遍歷找到要獲取的結點,如果沒有找到,直接訪回null。 對hash鏈進行遍歷不需要加鎖的原因在於鏈指針next是final的。但是頭指針卻不是final的,這是通過getFirst(hash)方法返回,也就是存在 table數組中的值。這使得getFirst(hash)可能返回過時的頭結點,例如,當執行get方法時,剛執行完getFirst(hash)之後,另一個線程執行了刪除操作並更新頭結點,這就導致get方法中返回的頭結點不是最新的。這是可以允許,通過對count變量的協調機制,get能讀取到幾乎最新的數據,雖然可能不是最新的。要得到最新的數據,只有采用完全的同步。 2.) put 該方法也是在持有段鎖(鎖定當前segment)的情況下執行的,這當然是為了並發的安全,修改數據是不能並發進行的,必須得有個判斷是否超限的語句以確保容量不足時能夠rehash。首先根據計算得到的散列值定位到segment及該segment中的散列桶中。接著判斷是否存在同樣一個key的結點,如果存在就直接替換這個結點的值。否則創建一個新的結點並添加到hash鏈的頭部,這時一定要修改modCount和count的值,同樣修改count的值一定要放在最後一步。 3)remove HashEntry中除了value不是final的,其它值都是final的,這意味著不能從hash鏈的中間或尾部添加或刪除節點,因為這需要修改next 引用值,所有的節點的修改只能從頭部開始。對於put操作,可以一律添加到Hash鏈的頭部。但是對於remove操作,可能需要從中間刪除一個節點,這就需要將要刪除節點的前面所有節點整個復制一遍,最後一個節點指向要刪除結點的下一個結點。 首先定位到要刪除的節點e。如果不存在這個節點就直接返回null,否則就要將e前面的結點復制一遍,尾結點指向e的下一個結點。e後面的結點不需要復制,它們可以重用。 4) size() 每個Segment都有一個count變量,是一個volatile變量。當調用size方法時,首先先嘗試2次通過不鎖住segment的方式統計各個Segment的count值得總和,如果兩次值不同則將鎖住整個ConcurrentHashMap然後進行計算。 參見《java並發編程的藝術》P156 http://www.cnblogs.com/ITtangtang/p/3948786.html

線程的幾種可用狀態

線程在運行周期裏有6中不同的狀態。

  1. New 新建
  2. RUNNABLE 運行狀態,操作系統中運行與就緒兩種狀態統稱運行中
  3. BLOCKED 阻塞狀態
  4. WAITING 等待狀態
  5. TIME_WAITING 超時等待
  6. TERMINATED 終止狀態

同步方法和同步代碼塊的區別是什麽

  1. 同步方法只能鎖定當前對象或class對象, 而同步方法塊可以使用其他對象、當前對象及當前對象的class作為鎖。
  2. 從反編譯後的結果看,對於同步塊使用了monitorenter和monitorexit指令,而同步方法則是依靠方法上的修飾符ACC_SYNCHRONIZED來完成,但它們的本質都是對一個對象監視器進行獲取,而這個獲取過程是排他的。

顯示鎖ReentrantLock與內置鎖synchronized的相同與區別

相同:顯示鎖與內置鎖在加鎖和內存上提供的語義相同(互斥訪問臨界區) 不同: 1.使用方式,內置無需指定釋放鎖,簡化鎖操作。顯示鎖擁有鎖獲取和釋放的可操作性。 2.功能上:顯示鎖提供了其他很多功能如定時鎖等待、可中斷鎖等待、公平性、嘗試非阻塞獲取鎖、以及實現非結構化的加鎖。(一個線程獲取不到鎖而被阻塞在synchronized之外時,對該線程進行中斷操作,此時該線程的中斷表示為會被修改,但線程依舊會被阻塞在synchronized上,等待獲取鎖。) 3.對死鎖的處理:內置只能重啟,顯示可以通過設置超時獲取鎖來避免 4.性能上:java1.5 顯示遠超內置,java1.6 顯示鎖稍微比內置好

  1. atomicinteger和Volatile等線程安全操作的關鍵字的理解和使用

SOF你遇到過哪些情況。

  1. 實現多線程的3種方法:Thread與Runable。 1)繼承Tread類,重寫run函數 2)實現Runnable接口 3)實現Callable接口
  1. 線程同步的方法:sychronized、lock、reentrantLock等。

  2. 鎖的等級:方法鎖、對象鎖、類鎖。

  3. ThreadPool用法與優勢。

26 Callable和Runnable的區別

  1. Concurrent包裏的其他東西:ArrayBlockingQueue、CountDownLatch等等。

  2. foreach與正常for循環效率對比。

  3. 反射的作用於原理。

  4. 泛型常用特點,List能否轉為List。

  5. 設計模式:單例、工廠、適配器、責任鏈、觀察者等等。

  6. JNI的使用。 java的代理是怎麽實現的
  7. Java1.7與1.8新特性。 lmbda表達式 Java8新特性 連接池使用使用什麽數據結構實現 實現連接池 結束一條 Thread 有什麽方法? interrupt 底層實現有看過嗎?線程的狀態是怎麽樣的?如果給你實現會怎麽樣做? Java 中有內存泄露嗎?是怎麽樣的情景?為什麽不用循環計數? java都有哪些加鎖方式 AIO與BIO的區別 生產者與消費者,手寫代碼
  8. Java創建線程之後,直接調用start()方法和run()的區別
  9. 常用的線程池模式以及不同線程池的使用場景
  10. newFixedThreadPool此種線程池如果線程數達到最大值後會怎麽辦,底層原理。
  11. 多線程之間通信的同步問題,synchronized鎖的是對象,衍伸出和synchronized相關很多的具體問題,例如同一個類不同方法都有synchronized鎖,一個對象是否可以同時訪問。或者一個類的static構造方法加上synchronized之後的鎖的影響。
  12. 了解可重入鎖的含義,以及ReentrantLock 和synchronized的區別
  13. 同步的數據結構,例如concurrentHashMap的源碼理解以及內部實現原理,為什麽他是同步的且效率高
  14. 線程間通信,wait和notify

  15. 定時線程的使用

  16. 場景:在一個主線程中,要求有大量(很多很多)子線程執行完之後,主線程才執行完成。多種方式,考慮效率。
  17. 並發、同步的接口或方法
  18. J.U.C下的常見類的使用。 ThreadPool的深入考察; BlockingQueue的使用。(take,poll的區別,put,offer的區別);原子類的實現。
  19. 簡單介紹下多線程的情況,從建立一個線程開始。然後怎麽控制同步過程,多線程常用的方法和結構
  20. 實現多線程有幾種方式,多線程同步怎麽做,說說幾個線程裏常用的方法

寫出生產者消費者模式。

public class ProducerConsumerPattern {
    public static void main(String args[]){
     BlockingQueue sharedQueue = new LinkedBlockingQueue();
     Thread prodThread = new Thread(new Producer(sharedQueue));
     Thread consThread = new Thread(new Consumer(sharedQueue));
     prodThread.start();
     consThread.start();
    }
}

//Producer Class in java
class Producer implements Runnable {
    private final BlockingQueue sharedQueue;
    public Producer(BlockingQueue sharedQueue) {
        this.sharedQueue = sharedQueue;
    }
    @Override
    public void run() {
        for(int i=0; i<10; i++){
            try {
                System.out.println("Produced: " + i);
                sharedQueue.put(i);
            } catch (InterruptedException ex) {
                Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

//Consumer Class in Java
class Consumer implements Runnable{
    private final BlockingQueue sharedQueue;
    public Consumer (BlockingQueue sharedQueue) {
        this.sharedQueue = sharedQueue;
    }
    @Override
    public void run() {
        while(true){
            try {
                System.out.println("Consumed: "+ sharedQueue.take());
            } catch (InterruptedException ex) {
                Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

Java面試準備之多線程