Java並發編程相關的面試題及答案分享

基礎概念

1.什麼是原子操作?在Java Concurrency API中有哪些原子類(atomic classes)?

原子操作(atomic operation)意為"不可被中斷的一個或一系列操作"。

處理器使用基於對緩存加鎖或總線加鎖的方式來實現多處理器之間的原子操作。

在Java中可以通過鎖和循環CAS的方式來實現原子操作。CAS操作——Compare & Set,或是 Compare & Swap,現在幾乎所有的CPU指令都支援CAS的原子操作。

原子操作是指一個不受其他操作影響的操作任務單元。原子操作是在多線程環境下避免數據不一致必須的手段。

int++並不是一個原子操作,所以當一個線程讀取它的值並加1時,另外一個線程有可能會讀到之前的值,這就會引發錯誤。

為瞭解決這個問題,必須保證增加操作是原子的,在JDK1.5之前我們可以使用同步技術來做到這一點。到JDK1.5,java.util.concurrent.atomic包提供瞭int和long類型的原子包裝類,它們可以自動的保證對於他們的操作是原子的並且不需要使用同步。

java.util.concurrent這個包裡面提供瞭一組原子類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇一個另一個線程進入,這隻是一種邏輯上的理解。

AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

AtomicIntegerArray,AtomicLongArray

AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray

2.Java Concurrency API中的Lock接口(Lock interface)是什麼?對比同步它有什麼優勢?

Lock接口比同步方法和同步塊提供瞭更具擴展性的鎖操作。

他們允許更靈活的結構,可以具有完全不同的性質,並且可以支援多個相關類的條件對象。

它的優勢有:

可以使鎖更公平

可以使線程在等待鎖的時候響應中斷

可以讓線程嘗試獲取鎖,並在無法獲取鎖的時候立即返回或者等待一段時間

可以在不同的范圍,以不同的順序獲取和釋放鎖

3.什麼是Executors框架?

Executor框架是一個根據一組執行策略調用,調度,執行和控制的異步任務的框架。

無限制的創建線程會引起應用程式內存溢出。所以創建一個線程池是個更好的的解決方案,因為可以限制線程的數量並且可以回收再利用這些線程。利用Executors框架可以非常方便的創建一個線程池。

4.什麼是阻塞隊列?阻塞隊列的實現原理是什麼?如何使用阻塞隊列來實現生產者-消費者模型?

阻塞隊列(BlockingQueue)是一個支援兩個附加操作的隊列。

這兩個附加的操作是:在隊列為空時,獲取元素的線程會等待隊列變為非空。當隊列滿時,存儲元素的線程會等待隊列可用。

阻塞隊列常用於生產者和消費者的場景,生產者是往隊列裡添加元素的線程,消費者是從隊列裡拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也隻從容器裡拿元素。

JDK7提供瞭7個阻塞隊列。分別是:

ArrayBlockingQueue :一個由數組結構組成的有界阻塞隊列。

LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列。

PriorityBlockingQueue :一個支援優先級排序的無界阻塞隊列。

DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。

SynchronousQueue:一個不存儲元素的阻塞隊列。

LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。

LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

Java 5之前實現同步存取時,可以使用普通的一個集合,然後在使用線程的協作和線程同步可以實現生產者,消費者模式,主要的技術就是用好,wait ,notify,notifyAll,sychronized這些關鍵字。而在java 5之後,可以使用阻塞隊列來實現,此方式大大簡少瞭代碼量,使得多線程編程更加容易,安全方面也有保障。

BlockingQueue接口是Queue的子接口,它的主要用途並不是作為容器,而是作為線程同步的的工具,因此他具有一個很明顯的特性,當生產者線程試圖向BlockingQueue放入元素時,如果隊列已滿,則線程被阻塞,當消費者線程試圖從中取出一個元素時,如果隊列為空,則該線程會被阻塞,正是因為它所具有這個特性,所以在程式中多個線程交替向BlockingQueue中放入元素,取出元素,它可以很好的控制線程之間的通信。

阻塞隊列使用最經典的場景就是socket客戶端數據的讀取和解析,讀取數據的線程不斷將數據放入隊列,然後解析線程不斷從隊列取數據解析。

5.什麼是Callable和Future

Callable接口類似於Runnable,從名字就可以看出來瞭,

但是Runnable不會返回結果,並且無法拋出返回結果的異常,而Callable功能更強大一些,被線程執行後,可以返回值,這個返回值可以被Future拿到,也就是說,Future可以拿到異步執行任務的返回值。

可以認為是帶有回調的Runnable。

Callable接口代表一段可以調用並返回結果的代碼;Future接口表示異步任務,是還沒有完成的任務給出的未來結果。所以說Callable用於產生結果,Future用於獲取結果。

6.什麼是FutureTask使用ExecutorService啟動任務。

在Java並發程式中FutureTask表示一個可以取消的異步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結果等方法。隻有當運算完成的時候結果才能取回,如果運算尚未完成get方法將會阻塞。一個FutureTask對象可以對調用瞭Callable和Runnable的對象進行包裝,由於FutureTask也是調用瞭Runnable接口所以它可以提交給Executor來執行。

7.什麼是並發容器的實現?

何為同步容器:可以簡單地理解為通過synchronized來實現同步的容器,如果有多個線程調用同步容器的方法,它們將會串行執行。比如Vector,Hashtable,以及Collections.synchronizedSet,synchronizedList等方法返回的容器。

可以通過查看Vector,Hashtable等這些同步容器的實現代碼,可以看到這些容器實現線程安全的方式就是將它們的狀態封裝起來,並在需要同步的方法上加上關鍵字synchronized。

並發容器使用瞭與同步容器完全不同的加鎖策略來提供更高的並發性和伸縮性,例如在ConcurrentHashMap中采用瞭一種粒度更細的加鎖機制,可以稱為分段鎖,在這種鎖機制下,允許任意數量的讀線程並發地訪問map,並且執行讀操作的線程和寫操作的線程也可以並發的訪問map,同時允許一定數量的寫操作線程並發地修改map,所以它可以在並發環境下實現更高的吞吐量。

8.多線程同步和互斥有幾種實現方法,都是什麼?

線程同步是指線程之間所具有的一種制約關系,一個線程的執行依賴另一個線程的消息,當它沒有得到另一個線程的消息時應等待,直到消息到達時才被喚醒。

線程互斥是指對於共享的進程系統資源,在各單個線程訪問時的排它性。當有若幹個線程都要使用某一共享資源時,任何時刻最多隻允許一個線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。線程互斥可以看成是一種特殊的線程同步。

線程間的同步方法大體可分為兩類:用戶模式和內核模式。顧名思義,內核模式就是指利用系統內核對象的單一性來進行同步,使用時需要切換內核態與用戶態,而用戶模式就是不需要切換到內核態,隻在用戶態完成操作。

用戶模式下的方法有:原子操作(例如一個單一的全局變量),臨界區。內核模式下的方法有:事件,信號量,互斥量。

9.什麼是競爭條件?你怎樣發現和解決競爭?

當多個進程都企圖對共享數據進行某種處理,而最後的結果又取決於進程運行的順序時,則我們認為這發生瞭競爭條件(race condition)。

10.你將如何使用thread dump?你將如何分析Thread dump?

java.lang.Thread.State枚舉中定義瞭線程的幾種狀態:

NEW:線程剛被創建,但是還沒有被處理。

RUNNABLE:線程占用瞭 CPU 並且處理瞭一個任務。(或是是在等待狀態由於操作系統的資源分配)

BLOCKED:該線程正在等待另外的不同的線程釋放鎖,以便獲取監視器鎖

WAITING:該線程正在等待,通過使用瞭 wait, join 或者是 park 方法

TIMED_WAITING:該線程正在等待,通過使用瞭 sleep, wait, join 或者是 park 方法。(這個與 WAITING 不同是通過方法參數指定瞭最大等待時間,WAITING 可以通過時間或者是外部的變化解除)

11.為什麼我們調用start()方法時會執行run()方法,為什麼我們不能直接調用run()方法?

當你調用start()方法時你將創建新的線程,並且執行在run()方法裡的代碼。但是如果你直接調用run()方法,它不會創建新的線程也不會執行調用線程的代碼。

12.Java中你怎樣喚醒一個阻塞的線程?

在Java發展史上曾經使用suspend()、resume()方法對於線程進行阻塞喚醒,但隨之出現很多問題,比較典型的還是死鎖問題。

解決方案可以使用以對象為目標的阻塞,即利用Object類的wait()和notify()方法實現線程阻塞。

首先,wait、notify方法是針對對象的,調用任意對象的wait()方法都將導致線程阻塞,阻塞的同時也將釋放該對象的鎖,相應地,調用任意對象的notify()方法則將隨機解除該對象阻塞的線程,但它需要重新獲取改對象的鎖,直到獲取成功才能往下執行;其次,wait、notify方法必須在synchronized塊或方法中被調用,並且要保證同步塊或方法的鎖對象與調用wait、notify方法的對象是同一個,如此一來在調用wait之前當前線程就已經成功獲取某對象的鎖,執行wait阻塞後當前線程就將之前獲取的對象鎖釋放。

14.在Java中CycliBarriar和CountdownLatch有什麼區別?

CyclicBarrier可以重復使用,而CountdownLatch不能重復使用。

Java的concurrent包裡面的CountDownLatch其實可以把它看作一個計數器,隻不過這個計數器的操作是原子操作,同時隻能有一個線程去操作這個計數器,也就是同時隻能有一個線程去減這個計數器裡面的值。

你可以向CountDownLatch對象設置一個初始的數字作為計數值,任何調用這個對象上的await()方法都會阻塞,直到這個計數器的計數值被其他的線程減為0為止。

所以在當前計數到達零之前,await 方法會一直受阻塞。之後,會釋放所有等待的線程,await的所有後續調用都將立即返回。這種現象隻出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。

CountDownLatch的一個非常典型的應用場景是:有一個任務想要往下執行,但必須要等到其他的任務執行完畢後才可以繼續往下執行。假如我們這個想要繼續往下執行的任務調用一個CountDownLatch對象的await()方法,其他的任務執行完自己的任務後調用同一個CountDownLatch對象上的countDown()方法,這個調用await()方法的任務將一直阻塞等待,直到這個CountDownLatch對象的計數值減到0為止。

CyclicBarrier一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點 (common barrier point)。在涉及一組固定大小的線程的程式中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 barrier 在釋放等待線程後可以重用,所以稱它為循環 的 barrier。

14.什麼是不可變對象,它對寫並發應用有什麼幫助?

不可變對象(Immutable Objects)即對象一旦被創建它的狀態(對象的數據,也即對象屬性值)就不能改變,反之即為可變對象(Mutable Objects)。

不可變對象的類即為不可變類(Immutable Class)。Java平臺類庫中包含許多不可變類,如String、基本類型的包裝類、BigInteger和BigDecimal等。

不可變對象天生是線程安全的。它們的常量(域)是在構造函數中創建的。既然它們的狀態無法修改,這些常量永遠不會變。

不可變對象永遠是線程安全的。

隻有滿足如下狀態,一個對象才是不可變的;

它的狀態不能在創建後再被修改;

所有域都是final類型;並且,

它被正確創建(創建期間沒有發生this引用的逸出)。

15.什麼是多線程中的上下文切換?

在上下文切換過程中,CPU會停止處理當前運行的程式,並保存當前程式運行的具體位置以便之後繼續運行。從這個角度來看,上下文切換有點像我們同時閱讀幾本書,在來回切換書本的同時我們需要記住每本書當前讀到的頁碼。在程式中,上下文切換過程中的“頁碼”信息是保存在進程控制塊(PCB)中的。PCB還經常被稱作“切換楨”(switchframe)。“頁碼”信息會一直保存到CPU的內存中,直到他們被再次使用。

上下文切換是存儲和恢復CPU狀態的過程,它使得線程執行能夠從中斷點恢復執行。上下文切換是多任務操作系統和多線程環境的基本特征。

16.Java中用到的線程調度算法是什麼?

計算機通常隻有一個CPU,在任意時刻隻能執行一條機器指令,每個線程隻有獲得CPU的使用權才能執行指令.所謂多線程的並發運行,其實是指從宏觀上看,各個線程輪流獲得CPU的使用權,分別執行各自的任務.在運行池中,會有多個處於就緒狀態的線程在等待CPU,JAVA虛擬機的一項任務就是負責線程的調度,線程調度是指按照特定機制為多個線程分配CPU的使用權.

有兩種調度模型:分時調度模型和搶占式調度模型。

分時調度模型是指讓所有的線程輪流獲得cpu的使用權,並且平均分配每個線程占用的CPU的時間片這個也比較好理解。

java虛擬機采用搶占式調度模型,是指優先讓可運行池中優先級高的線程占用CPU,如果可運行池中的線程優先級相同,那麼就隨機選擇一個線程,使其占用CPU。處於運行狀態的線程會一直運行,直至它不得不放棄CPU。

17.什麼是線程組,為什麼在Java中不推薦使用?

線程組和線程池是兩個不同的概念,他們的作用完全不同,前者是為瞭方便線程的管理,後者是為瞭管理線程的生命周期,復用線程,減少創建銷毀線程的開銷。

18.為什麼使用Executor框架比使用應用創建和管理線程好?

19.java中有幾種方法可以實現一個線程?

20.如何停止一個正在運行的線程?

21.notify()和notifyAll()有什麼區別?

22.什麼是Daemon線程?它有什麼意義?

23.java如何實現多線程之間的通訊和協作?

24.什麼是可重入鎖(ReentrantLock)?

25.當一個線程進入某個對象的一個synchronized的實例方法後,其它線程是否可進入此對象的其它方法?

26.synchronized和java.util.concurrent.locks.Lock的異同?

27.樂觀鎖和悲觀鎖的理解及如何實現,有哪些實現方式?

28.SynchronizedMap和ConcurrentHashMap有什麼區別?

29.CopyOnWriteArrayList可以用於什麼應用場景?

30.什麼叫線程安全?servlet是線程安全嗎

31.volatile有什麼用?能否用一句話說明下volatile的應用場景?

32.請說明下java的內存模型及其工作流程。

33.為什麼代碼會重排序?

34.現在有T1、T2、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行?

可以用join方法實現。

35.在Java中Lock接口比synchronized塊的優勢是什麼?你需要實現一個高效的緩存,它允許多個用戶讀,但隻允許一個用戶寫,以此來保持它的完整性,你會怎樣去實現它?

lock接口在多線程和並發編程中最大的優勢是它們為讀和寫分別提供瞭鎖,它能滿足你寫像ConcurrentHashMap這樣的高性能數據結構和有條件的阻塞。

36.在java中wait和sleep方法的不同?

最大的不同是在等待時wait會釋放鎖,而sleep一直持有鎖。Wait通常被用於線程間交互,sleep通常被用於暫停執行。

37.用Java實現阻塞隊列。

38.用Java實現代碼來解決生產者——消費者問題。

39.用Java編程一個會導致死鎖的程式,你將怎麼解決?

40.什麼是原子操作,Java中的原子操作是什麼?

41.Java中的volatile關鍵是什麼作用?怎樣使用它?在Java中它跟synchronized方法有什麼不同?

42.一個線程運行時發生異常會怎樣?

如果異常沒有被捕獲該線程將會停止執行。Thread.UncaughtExceptionHandler是用於處理未捕獲異常造成線程突然中斷情況的一個內嵌接口。當一個未捕獲異常將造成線程中斷的時候JVM會使用Thread.getUncaughtExceptionHandler()來查詢線程的UncaughtExceptionHandler並將線程和異常作為參數傳遞給handler的uncaughtException()方法進行處理。

43.如何在兩個線程間共享數據?

44.Java中notify 和 notifyAll有什麼區別?

notify() 方法不能喚醒某個具體的線程,所以隻有一個線程在等待的時候它才有用武之地。而notifyAll()喚醒所有線程並允許他們爭奪鎖確保瞭至少有一個線程能繼續運行。

45.為什麼wait, notify 和 notifyAll這些方法不在thread類裡面?

一個很明顯的原因是JAVA提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。由於wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因為鎖屬於對象。

46.什麼是ThreadLocal變量?

ThreadLocal是Java裡一種特殊的變量。每個線程都有一個ThreadLocal就是每個線程都擁有瞭自己獨立的一個變量,競爭條件被徹底消除瞭。它是為創建代價高昂的對象獲取線程安全的好方法,比如你可以用ThreadLocal讓SimpleDateFormat變成線程安全的,因為那個類創建代價高昂且每次調用都需要創建不同的實例所以不值得在局部范圍使用它,如果為每個線程提供一個自己獨有的變量拷貝,將大大提高效率。首先,通過復用減少瞭代價高昂的對象的創建個數。其次,你在沒有使用高代價的同步或者不變性的情況下獲得瞭線程安全。

47.Java中interrupted 和 isInterruptedd方法的區別?

48.為什麼wait和notify方法要在同步塊中調用?

Java API強制要求這樣做,如果你不這麼做,你的代碼會拋出IllegalMonitorStateException異常。還有一個原因是為瞭避免wait和notify之間產生競態條件。

49.為什麼你應該在循環中檢查等待條件

處於等待狀態的線程可能會收到錯誤警報和偽喚醒,如果不在循環中檢查等待條件,程式就會在沒有滿足結束條件的情況下退出。

50.Java中的同步集合與並發集合有什麼區別?

同步集合與並發集合都為多線程和並發提供瞭合適的線程安全的集合,不過並發集合的可擴展性更高。在Java1.5之前程式員們隻有同步集合來用且在多線程並發的時候會導致爭用,阻礙瞭系統的擴展性。Java5介紹瞭並發集合像ConcurrentHashMap,不僅提供線程安全還用鎖分離和內部分區等現代技術提高瞭可擴展性。

51.Java中堆和棧有什麼不同?

52.什麼是線程池? 為什麼要使用它?

創建線程要花費昂貴的資源和時間,如果任務來瞭才創建線程那麼響應時間會變長,而且一個進程能創建的線程數有限。為瞭避免這些問題,在程式啟動的時候就創建若幹線程來響應處理,它們被稱為線程池,裡面的線程叫工作線程。從JDK1.5開始,Java API提供瞭Executor框架讓你可以創建不同的線程池。

53.如何避免死鎖?

死鎖的發生必須滿足以下四個條件:

互斥條件:一個資源每次隻能被一個進程使用。

請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。

不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。

循環等待條件:若幹進程之間形成一種頭尾相接的循環等待資源關系。

避免死鎖最簡單的方法就是阻止循環等待條件,將系統中所有的資源設置標志位、排序,規定所有的進程申請資源必須以一定的順序(升序或降序)做操作來避免死鎖。

54.Java中活鎖和死鎖有什麼區別?

活鎖和死鎖類似,不同之處在於處於活鎖的線程或進程的狀態是不斷改變的,活鎖可以認為是一種特殊的饑餓。一個現實的活鎖例子是兩個人在狹小的走廊碰到,兩個人都試著避讓對方好讓彼此通過,但是因為避讓的方向都一樣導致最後誰都不能通過走廊。簡單的說就是,活鎖和死鎖的主要區別是前者進程的狀態可以改變但是卻不能繼續執行。

55.怎麼檢測一個線程是否擁有鎖?

在java.lang.Thread中有一個方法叫holdsLock(),它返回true如果當且僅當當前線程擁有某個具體對象的鎖。

56.你如何在Java中獲取線程堆棧?

57.JVM中哪個參數是用來控制線程的棧堆棧小的

58.Java中synchronized 和 ReentrantLock 有什麼不同?

59.Thread類中的yield方法有什麼作用?

60.Java中ConcurrentHashMap的並發度是什麼?

ConcurrentHashMap把實際map劃分成若幹部分來實現它的可擴展性和線程安全。這種劃分是使用並發度獲得的,它是ConcurrentHashMap類構造函數的一個可選參數,默認值為16,這樣在多線程情況下就能避免爭用。

61.Java中Semaphore是什麼?

Java中的Semaphore是一種新的同步類,它是一個計數信號。從概念上講,從概念上講,信號量維護瞭一個許可集合。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release()添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,Semaphore隻對可用許可的號碼進行計數,並采取相應的行動。信號量常常用於多線程的代碼中,比如數據庫連接池。

62.Java線程池中submit() 和 execute()方法有什麼區別?

兩個方法都可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展瞭Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。

63.如何在Java中創建Immutable對象?

64.什麼是阻塞式方法?

阻塞式方法是指程式會一直等待該方法完成期間不做其他事情,ServerSocket的accept()方法就是一直等待客戶端連接。這裡的阻塞是指調用結果返回之前,當前線程會被掛起,直到得到結果之後才會返回。此外,還有異步和非阻塞式方法在任務完成前就返回。

65.Java中的ReadWriteLock是什麼?

讀寫鎖是用來提升並發程式性能的鎖分離技術的成果。

66.volatile 變量和 atomic 變量有什麼不同?

Volatile變量可以確保先行關系,即寫操作會發生在後續的讀操作之前, 但它並不能保證原子性。例如用volatile修飾count變量那麼 count++ 操作就不是原子性的。而AtomicInteger類提供的atomic方法可以讓這種操作具有原子性如getAndIncrement()方法會原子性的進行增量操作把當前值加一,其它數據類型和引用變量也可以進行相似操作。

67.用戶線程和守護線程有什麼區別?

當我們在Java程式中創建一個線程,它就被稱為用戶線程。一個守護線程是在後臺執行並且不會阻止JVM終止的線程。當沒有用戶線程在運行的時候,JVM關閉程式並且退出。一個守護線程創建的子線程依然是守護線程。

68.線程生命周期有哪些階段?

當我們在Java程式中新建一個線程時,它的狀態是New。當我們調用線程的start()方法時,狀態被改變為Runnable。線程調度器會為Runnable線程池中的線程分配CPU時間並且將它們的狀態改變為Running。其他的線程狀態還有Waiting,Blicked 和Dead。

69.可以直接調用Thread類的run ()方法麼?

當然可以。但是如果我們調用瞭Thread的run()方法,它的行為就會和普通的方法一樣,會在當前線程中執行。為瞭在新的線程中執行我們的代碼,必須使用Thread.start()方法。

70.如何讓正在運行的線程暫停一段時間?

我們可以使用Thread類的Sleep()方法讓線程暫停一段時間。需要註意的是,這並不會讓線程終止,一旦從休眠中喚醒線程,線程的狀態將會被改變為Runnable,並且根據線程調度,它將得到執行。

71.你對線程優先級的理解是什麼?

每一個線程都是有優先級的,一般來說,高優先級的線程在運行時會具有優先權,但這依賴於線程調度的實現,這個實現是和操作系統相關的(OS dependent)。我們可以定義線程的優先級,但是這並不能保證高優先級的線程會在低優先級的線程前執行。線程優先級是一個int變量(從1-10),1代表最低優先級,10代表最高優先級。

72.什麼是線程調度器(Thread Scheduler)和時間分片(Time Slicing )?

線程調度器是一個操作系統服務,它負責為Runnable狀態的線程分配CPU時間。一旦我們創建一個線程並啟動它,它的執行便依賴於線程調度器的實現。時間分片是指將可用的CPU時間分配給可用的Runnable線程的過程。分配CPU時間可以基於線程優先級或者線程等待的時間。線程調度並不受到Java虛擬機控制,所以由應用程式來控制它是更好的選擇(也就是說不要讓你的程式依賴於線程的優先級)。

73.你如何確保main()方法所在的線程是Java 程式最後結束的線程?

我們可以使用Thread類的joint()方法來確保所有程式創建的線程在main()方法退出前結束。

74.線程之間是如何通信的?

當線程間是可以共享資源時,線程間通信是協調它們的重要的手段。Object類中wait()\notify()\notifyAll()方法可以用於線程間通信關於資源的鎖的狀態。

75. 為什麼線程通信的方法wait(), notify()和notifyAll()被定義在Object 類裡?

Java的每個對象中都有一個鎖(monitor,也可以成為監視器) 並且wait(),notify()等方法用於等待對象的鎖或者通知其他線程對象的監視器可用。在Java的線程中並沒有可供任何對象使用的鎖和同步器。這就是為什麼這些方法是Object類的一部分,這樣Java的每一個類都有用於線程間通信的基本方法。

76.為什麼wait(), notify()和notifyAll ()必須在同步方法或者同步塊中被調用?

當一個線程需要調用對象的wait()方法的時候,這個線程必須擁有該對象的鎖,接著它就會釋放這個對象鎖並進入等待狀態直到其他線程調用這個對象上的notify()方法。同樣的,當一個線程需要調用對象的notify()方法時,它會釋放這個對象的鎖,以便其他在等待的線程就可以得到這個對象鎖。由於所有的這些方法都需要線程持有對象的鎖,這樣就隻能通過同步來實現,所以他們隻能在同步方法或者同步塊中被調用。

77.為什麼Thread類的sleep()和yield ()方法是靜態的?

Thread類的sleep()和yield()方法將在當前正在執行的線程上運行。所以在其他處於等待狀態的線程上調用這些方法是沒有意義的。這就是為什麼這些方法是靜態的。它們可以在當前正在執行的線程中工作,並避免程式員錯誤的認為可以在其他非運行線程調用這些方法。

78.如何確保線程安全?

在Java中可以有很多方法來保證線程安全——同步,使用原子類(atomic concurrent classes),實現並發鎖,使用volatile關鍵字,使用不變類和線程安全類。

79.volatile關鍵字在Java 中有什麼作用?

當我們使用volatile關鍵字去修飾變量的時候,所以線程都會直接讀取該變量並且不緩存它。這就確保瞭線程讀取到的變量是同內存中是一致的。

80.同步方法和同步塊,哪個是更好的選擇?

同步塊是更好的選擇,因為它不會鎖住整個對象(當然你也可以讓它鎖住整個對象)。同步方法會鎖住整個對象,哪怕這個類中有多個不相關聯的同步塊,這通常會導致他們停止執行並需要等待獲得這個對象上的鎖。

81.如何創建守護線程?

使用Thread類的setDaemon(true)方法可以將線程設置為守護線程,需要註意的是,需要在調用start()方法前調用這個方法,否則會拋出IllegalThreadStateException異常。

82. 什麼是ThreadLocal

ThreadLocal用於創建線程的本地變量,我們知道一個對象的所有線程會共享它的全局變量,所以這些變量不是線程安全的,我們可以使用同步技術。但是當我們不想使用同步的時候,我們可以選擇ThreadLocal變量。

每個線程都會擁有他們自己的Thread變量,它們可以使用get()\set()方法去獲取他們的默認值或者在線程內部改變他們的值。ThreadLocal實例通常是希望它們同線程狀態關聯起來是private static屬性。在ThreadLocal例子這篇文章中你可以看到一個關於ThreadLocal的小程式。

83.什麼是Thread Group ?為什麼建議使用它?

ThreadGroup是一個類,它的目的是提供關於線程組的信息。

ThreadGroup API比較薄弱,它並沒有比Thread提供瞭更多的功能。它有兩個主要的功能:一是獲取線程組中處於活躍狀態線程的列表;二是設置為線程設置未捕獲異常處理器(ncaught exception handler)。但在Java 1.5中Thread類也添加瞭setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 方法,所以ThreadGroup是已經過時的,不建議繼續使用。

t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){

@Override

public void uncaughtException(Thread t, Throwable e) {

System.out.println("exception occured:"+e.getMessage());

}

});

84. 什麼是Java線程轉儲(Thread Dump ),如何得到它?

線程轉儲是一個JVM活動線程的列表,它對於分析系統瓶頸和死鎖非常有用。有很多方法可以獲取線程轉儲——使用Profiler,Kill -3命令,jstack工具等等。我更喜歡jstack工具,因為它容易使用並且是JDK自帶的。由於它是一個基於終端的工具,所以我們可以編寫一些腳本去定時的產生線程轉儲以待分析。

85.什麼是Java Timer 類?如何創建一個有特定時間間隔的任務?

java.util.Timer是一個工具類,可以用於安排一個線程在未來的某個特定時間執行。Timer類可以用安排一次性任務或者周期任務。

java.util.TimerTask是一個實現瞭Runnable接口的抽象類,我們需要去繼承這個類來創建我們自己的定時任務並使用Timer去安排它的執行。

目前有開源的Qurtz可以用來創建定時任務。

應用場景

(一)並發容器和框架

1.如何讓一段程式並發的執行,並最終匯總結果?

2.如何合理的配置java線程池?如CPU密集型的任務,基本線程池應該配置多大?IO密集型的任務,基本線程池應該配置多大?用有界隊列好還是無界隊列好?任務非常多的時候,使用什麼阻塞隊列能獲取最好的吞吐量?

3.如何使用阻塞隊列實現一個生產者和消費者模型?請寫代碼。

4.多讀少寫的場景應該使用哪個並發容器,為什麼使用它?

比如你做瞭一個搜索引擎,搜索引擎每次搜索前需要判斷搜索關鍵詞是否在黑名單裡,黑名單每天更新一次。

(二)Java中的鎖

1.如何實現樂觀鎖(CAS)?如何避免ABA問題?

2.讀寫鎖可以用於什麼應用場景?

3.什麼時候應該使用可重入鎖?

4.什麼場景下可以使用volatile替換synchronized?

(三)並發工具

1.如何實現一個流控程式,用於控制請求的調用次數?

幾道筆試題目

1.(百度筆試題)以下多線程對int型變量x的操作,哪幾個不需要進行同步:

A. x=y; B. x++; C. ++x; D. x=1;

2.(阿裡巴巴筆試題)多線程中棧與堆是公有的還是私有的

A:棧公有, 堆私有

B:棧公有,堆公有

C:棧私有, 堆公有

D:棧私有,堆私有

3.一個全局變量tally,兩個線程並發執行(代碼段都是ThreadProc),問兩個線程都結束後,tally取值范圍。

inttally = 0;//glable

voidThreadProc()

{

for(inti = 1; i <= 50; i++)

tally += 1;

}

4.子線程循環 10 次,接著主線程循環 100 次,接著又回到子線程循環 10 次,接著再回到主線程又循環 100 次,如此循環50次,試寫出代碼。

5.(迅雷筆試題):編寫一個程式,開啟3個線程,這3個線程的ID分別為A、B、C,每個線程將自己的ID在螢幕上打印10遍,要求輸出結果必須按ABC的順序顯示;如:ABCABC….依次遞推。

6.(Google面試題)有四個線程1、2、3、4。線程1的功能就是輸出1,線程2的功能就是輸出2,以此類推………現在有四個档案ABCD。初始都為空。現要讓四個档案呈如下格式:

A:1 2 3 4 1 2….

B:2 3 4 1 2 3….

C:3 4 1 2 3 4….

D:4 1 2 3 4 1….

請設計程式。

7.啟動3個線程打印遞增的數字, 線程1先打印1,2,3,4,5, 然後是線程2打印6,7,8,9,10, 然後是線程3打印11,12,13,14,15. 接著再由線程1打印16,17,18,19,20….以此類推, 直到打印到75. 程式的輸出結果應該為:

線程1: 1

線程1: 2

線程1: 3

線程1: 4

線程1: 5

線程2: 6

線程2: 7

線程2: 8

線程2: 9

線程2: 10

線程3: 71

線程3: 72

線程3: 73

線程3: 74

線程3: 75

8.在Java中創建線程安全的Singleton。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。