併發編程專題十三-併發編程相關面試題彙總

目錄

一、談面試

二、併發編程常見面試題

1、在java中守護線程和用戶線程的區別?

2、線程與進程的區別

3、什麼是多線程中的上下文切換

4、死鎖與活鎖的區別,死鎖與飢餓的區別?

5、synchronized底層實現原理

6、什麼是線程組,爲什麼在Java中不推薦使用?

7、什麼是Executors框架?爲什麼使用Executor框架?

8、在Java中Executor和Executors的區別?

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

10、Java Concurrency API中的Lock接口(Lock interface)是什麼?對比synchronized它有什麼優勢?

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

12、什麼是Callable和Future?

13、什麼是FutureTask?

14、什麼是併發容器的實現?

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

16、什麼是競爭條件?

17、爲什麼我們調用start()方法時會執行run()方法,爲什麼我們不能直接調用run()方法?

18、在Java中CycliBarriar和CountdownLatch有什麼區別?

19、什麼是不可變對象,它對寫併發應用有什麼幫助?

20、notify()和notifyAll()有什麼區別?

21、什麼是可重入鎖(ReentrantLock)?談談它的實現。

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

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

24、什麼是CAS操作,缺點是什麼?

25、SynchronizedMap和ConcurrentHashMap有什麼區別?

26、寫時複製容器可以用於什麼應用場景?

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

28、爲什麼代碼會重排序?

29、在java中wait和sleep方法的不同?

30、一個線程運行時發生異常會怎樣?

31、爲什麼wait, notify 和 notifyAll這些方法不在thread類裏面?

32、什麼是ThreadLocal變量?

33、Java中interrupted 和 isInterrupted方法的區別?

34、爲什麼wait和notify方法要在同步塊中調用?

35、爲什麼你應該在循環中檢查等待條件?

36、怎麼檢測一個線程是否擁有鎖?

37、你如何在Java中獲取線程堆棧?

38、java線程池中submit() 和 execute()方法有什麼區別?

39、你對線程優先級的理解是什麼?

40、你如何確保main()方法所在的線程是Java 程序最後結束的線程?

41、爲什麼Thread類的sleep()和yield ()方法是靜態的?

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

43、你需要實現一個高效的緩存,它允許多個用戶讀,但只允許一個用戶寫,以此來保持它的完整性,你會怎樣去實現它?

44、用Java寫代碼來解決生產者——消費者問題。

 45、Java中如何停止一個線程?

46、JVM中哪個參數是用來控制線程的棧堆棧大小的

47、如果同步塊內的線程拋出異常鎖會釋放嗎?

48、單例模式的雙重檢查實現是什麼?爲什麼並不安全?如何在Java中創建線程安全的Singleton?

49、寫出3條你遵循的多線程最佳實踐

50、請概述線程池的創建參數,怎麼樣合理配置一個線程池的參數?

51、請概述鎖的公平和非公平,JDK內部是如何實現的。

52、請概述AQS

53、請概述volatile


一、談面試

1.面試主要分爲兩塊:一塊是考查工程師對基礎知識(包括了技術廣度、深度、對技術的熱情度等)的掌握程度,因爲基礎知識決定了一個技術人員發展的上限;另一塊是考察工程師的工程能力,比如:做過哪些項目?遇到最難的問題怎樣解決的?說說最有成就感的一項任務?工程能力是考察工程師當下能爲公司帶來的利益。當然還有其它考覈方面:抗壓性、合作能力。

2.Java只是一門語言,即使是Java工程師也不能侷限於Java,要從面嚮對象語言本身,甚至從整個計算機體系,從工程實際出發看Java。

3.很多知識在一般公司的開發中是用不到的,常有人戲稱:“面試造火箭,工作擰螺絲”,但這只是通常情況下公司對程序員的標準——迅速產出,完成任務。所以,工程師爲了自己職業的發展不能侷限於公司對自己的要求,不能停留在應用層面,要能夠很好地掌握基礎知識,要多看源碼,自己多實踐,學成記得產出,比如多爲開源社區貢獻代碼,幫助初學者指路等。

其實“面試造火箭,工作擰螺絲”的背後其實是大家都普遍認可基礎知識的重要性。

二、併發編程常見面試題

1、在java中守護線程和用戶線程的區別?

java中的線程分爲兩種:守護線程(Daemon)和用戶線程(User)。

任何線程都可以設置爲守護線程和用戶線程,通過方法Thread.setDaemon(bool on);true則把該線程設置爲守護線程,反之則爲用戶線程。Thread.setDaemon()必須在Thread.start()之前調用,否則運行時會拋出異常。

兩者的區別:

唯一的區別是判斷虛擬機(JVM)何時離開,Daemon是爲其他線程提供服務,如果全部的User Thread已經結束,Daemon 沒有可服務的線程,JVM關閉。

擴展:Thread Dump打印出來的線程信息,含有daemon字樣的線程即爲守護進程

2、線程與進程的區別

進程是操作系統分配資源的最小單元,線程是操作系統調度的最小單元。

一個程序至少有一個進程,一個進程至少有一個線程。

3、什麼是多線程中的上下文切換

多線程會共同使用一組計算機上的CPU,而線程數大於給程序分配的CPU數量時,爲了讓各個線程都有執行的機會,就需要輪轉使用CPU。不同的線程切換使用CPU發生的切換數據等就是上下文切換。

4、死鎖與活鎖的區別,死鎖與飢餓的區別?

死鎖:是指兩個或兩個以上的進程(或線程)在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。

產生死鎖的必要條件:

互斥條件:所謂互斥就是進程在某一時間內獨佔資源。

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

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

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

活鎖:任務或者執行者沒有被阻塞,由於某些條件沒有滿足,導致一直重複嘗試,失敗,嘗試,失敗。

活鎖和死鎖的區別在於,處於活鎖的實體是在不斷的改變狀態,所謂的“活”, 而處於死鎖的實體表現爲等待;活鎖有可能自行解開,死鎖則不能。

飢餓:一個或者多個線程因爲種種原因無法獲得所需要的資源,導致一直無法執行的狀態。

5、synchronized底層實現原理

synchronized (this)原理:涉及兩條指令:monitorenter,monitorexit;再說同步方法,從同步方法反編譯的結果來看,方法的同步並沒有通過指令monitorenter和monitorexit來實現,相對於普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。

 

JVM就是根據該標示符來實現方法的同步的:當方法被調用時,調用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,如果設置了,執行線程將先獲取monitor,獲取成功之後才能執行方法體,方法執行完後再釋放monitor。在方法執行期間,其他任何線程都無法再獲得同一個monitor對象。

注意,這個問題可能會接着追問,java對象頭信息,偏向鎖,輕量鎖,重量級鎖及其他們相互間轉化。

6、什麼是線程組,爲什麼在Java中不推薦使用?

ThreadGroup類,可以把線程歸屬到某一個線程組中,線程組中可以有線程對象,也可以有線程組,組中還可以有線程,這樣的組織結構有點類似於樹的形式。

1.線程組ThreadGroup對象中比較有用的方法是stop、resume、suspend等方法,由於這幾個方法會導致線程的安全問題(主要是死鎖問題),已經被官方廢棄掉了,所以線程組本身的應用價值就大打折扣了。

2.線程組ThreadGroup不是線程安全的,這在使用過程中獲取的信息並不全是及時有效的,這就降低了它的統計使用價值。

7、什麼是Executors框架?爲什麼使用Executor框架?

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

每次執行任務創建線程 new Thread()比較消耗性能,創建一個線程是比較耗時、耗資源的。

調用 new Thread()創建的線程缺乏管理,而且可以無限制的創建,線程之間的相互競爭會導致過多佔用系統資源而導致系統癱瘓,還有線程之間的頻繁交替也會消耗很多系統資源。

接使用new Thread() 啓動的線程不利於擴展,比如定時執行、定期執行、定時定期執行、線程中斷等都不便實現。

8、在Java中Executor和Executors的區別?

Executors 工具類的不同方法按照我們的需求創建了不同的線程池,來滿足業務的需求。

Executor 接口對象能執行我們的線程任務。

ExecutorService接口繼承了Executor接口並進行了擴展,提供了更多的方法我們能獲得任務執行的狀態並且可以獲取任務的返回值。

使用ThreadPoolExecutor 可以創建自定義線程池。

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

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

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

 

在Java中可以通過鎖和循環CAS的方式來實現原子操作。 CAS操作——Compare & Set,或是 Compare & Swap,現在幾乎所有的CPU指令都支持CAS的原子操作。java.util.concurrent.atomic下提供了大量的原子操作類,比如原子類:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference ,原子數組:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray ,原子屬性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

10、Java Concurrency API中的Lock接口(Lock interface)是什麼?對比synchronized它有什麼優勢?

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

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

它的優勢有:可以使鎖更公平,可以使線程在等待鎖的時候響應中斷,可以讓線程嘗試獲取鎖,並在無法獲取鎖的時候立即返回或者等待一段時間,可以在不同的範圍,以不同的順序獲取和釋放鎖。

整體上來說Lock是synchronized的擴展版,Lock提供了無條件的、可輪詢的(tryLock方法)、定時的(tryLock帶參方法)、可中斷的(lockInterruptibly)、可多條件隊列的(newCondition方法)鎖操作。另外Lock的實現類基本都支持非公平鎖(默認)和公平鎖,synchronized只支持非公平鎖,當然,在大部分情況下,非公平鎖是高效的選擇。

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

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

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

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

JDK7提供了7個阻塞隊列。在實現上,主要是利用了Condition和Lock的等待通知模式。

12、什麼是Callable和Future?

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

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

Future接口表示異步任務,是還沒有完成的任務給出的未來結果。所以說Callable用於產生結果,Future用於獲取結果。

13、什麼是FutureTask?

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

14、什麼是併發容器的實現?

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

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

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

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

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

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

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

16、什麼是競爭條件?

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

17、爲什麼我們調用start()方法時會執行run()方法,爲什麼我們不能直接調用run()方法?

當你調用start()方法時你將創建新的線程,並且執行在run()方法裏的代碼。 

但是如果你直接調用run()方法,它不會創建新的線程也不會執行調用線程的代碼,只會把run方法當作普通方法去執行。

18、在Java中CycliBarriar和CountdownLatch有什麼區別?

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

19、什麼是不可變對象,它對寫併發應用有什麼幫助?

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

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

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

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

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

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

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

它被正確創建

20、notify()和notifyAll()有什麼區別?

當一個線程進入wait之後,就必須等其他線程notify/notifyall,使用notifyall,可以喚醒所有處於wait狀態的線程,使其重新進入鎖的爭奪隊列中,而notify只能喚醒一個。

如果沒把握,建議notifyAll,防止notigy因爲信號丟失而造成程序異常。

21、什麼是可重入鎖(ReentrantLock)?談談它的實現。

線程可以重複進入任何一個它已經擁有的鎖所同步着的代碼塊,synchronized、ReentrantLock都是可重入的鎖。在實現上,就是線程每次獲取鎖時判定如果獲得鎖的線程是它自己時,簡單將計數器累積即可,每 釋放一次鎖,進行計數器累減,直到計算器歸零,表示線程已經徹底釋放鎖。

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

如果其他方法沒有synchronized的話,其他線程是可以進入的。

所以要開放一個線程安全的對象時,得保證每個方法都是線程安全的。

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

悲觀鎖:總是假設最壞的情況,每次去拿數據的時候都認爲別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。Java裏面的同步原語synchronized關鍵字的實現是悲觀鎖。

樂觀鎖:顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。在Java中j原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。

樂觀鎖的實現方式: 

  • 使用版本標識來確定讀到的數據與提交時的數據是否一致。提交後修改版本標識,不一致時可以採取丟棄和再次嘗試的策略。 
  • java中的Compare and Swap即CAS ,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。

24、什麼是CAS操作,缺點是什麼?

CAS的基本思路就是,如果這個地址上的值和期望的值相等,則給其賦予新值,否則不做任何事兒,但是要返回原值是多少。每一個CAS操作過程都包含三個運算符:一個內存地址V,一個期望的值A和一個新值B,操作的時候如果這個地址上存放的值等於這個期望的值A,則將地址上的值賦爲新值B,否則不做任何操作。

CAS缺點:

ABA問題:

比如說一個線程one從內存位置V中取出A,這時候另一個線程two也從內存中取出A,並且two進行了一些操作變成了B,然後two又將V位置的數據變成A,這時候線程one進行CAS操作發現內存中仍然是A,然後one操作成功。儘管線程one的CAS操作成功,但可能存在潛藏的問題。從Java1.5開始JDK的atomic包裏提供了一個類AtomicStampedReference來解決ABA問題。

循環時間長開銷大:

對於資源競爭嚴重(線程衝突嚴重)的情況,CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低於synchronized。

只能保證一個共享變量的原子操作:

當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖。

25、SynchronizedMap和ConcurrentHashMap有什麼區別?

SynchronizedMap一次鎖住整張表來保證線程安全,所以每次只能有一個線程來訪爲map。

ConcurrentHashMap使用分段鎖來保證在多線程下的性能。

26、寫時複製容器可以用於什麼應用場景?

CopyOnWrite併發容器用於對於絕大部分訪問都是讀,且只是偶爾寫的併發場景。比如白名單,黑名單,商品類目的訪問和更新場景。

透露的思想

讀寫分離,讀和寫分開

最終一致性

使用另外開闢空間的思路,來解決併發衝突

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

volatile保證內存可見性和禁止指令重排。

volatile用於多線程環境下的一寫多讀,或者無關聯的多寫。

28、爲什麼代碼會重排序?

在執行程序時,爲了提供性能,處理器和編譯器常常會對指令進行重排序,但是不能隨意重排序,不是你想怎麼排序就怎麼排序,它需要滿足以下兩個條件:

在單線程環境下不能改變程序運行的結果;

存在數據依賴關係的不允許重排序

29、在java中wait和sleep方法的不同?

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

30、一個線程運行時發生異常會怎樣?

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

31、爲什麼wait, notify 和 notifyAll這些方法不在thread類裏面?

JAVA提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。如果線程需要等待某些鎖那麼調用對象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個鎖就不明顯了。簡單的說,由於wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因爲鎖屬於對象。

32、什麼是ThreadLocal變量?

ThreadLocal是Java裏一種特殊的變量。每個線程都有一個ThreadLocal就是每個線程都擁有了自己獨立的一個變量,競爭條件被徹底消除了。

33、Java中interrupted 和 isInterrupted方法的區別?

interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除而後者不會。Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識爲true。當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。

34、爲什麼wait和notify方法要在同步塊中調用?

主要是因爲Java API強制要求這樣做,如果你不這麼做,你的代碼會拋出IllegalMonitorStateException異常。

35、爲什麼你應該在循環中檢查等待條件?

處於等待狀態的線程可能會收到錯誤警報和僞喚醒,如果不在循環中檢查等待條件,程序就會在沒有滿足結束條件的情況下退出。因此,當一個等待線程醒來時,不能認爲它原來的等待狀態仍然是有效的,在notify()方法調用之後和等待線程醒來之前這段時間它可能會改變。這就是在循環中使用wait()方法效果更好的原因

36、怎麼檢測一個線程是否擁有鎖?

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

37、你如何在Java中獲取線程堆棧?

kill -3 [java pid]

不會在當前終端輸出,它會輸出到代碼執行的或指定的地方去。比如,kill -3 tomcat pid, 輸出堆棧到log目錄下。

Jstack [java pid]

這個比較簡單,在當前終端顯示,也可以重定向到指定文件中。

或者使用Java提供的擬機線程系統的管理接口ManagementFactory.getThreadMXBean()。

38、java線程池中submit() 和 execute()方法有什麼區別?

兩個方法都可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中。

而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口

39、你對線程優先級的理解是什麼?

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

java的線程優先級調度會委託給操作系統去處理,所以與具體的操作系統優先級有關,如非特別需要,一般無需設置線程優先級。

40、你如何確保main()方法所在的線程是Java 程序最後結束的線程?

可以使用Thread類的join()方法(或者CountDownLatch工具類)來確保所有程序創建的線程在main()方法退出前結束。

41、爲什麼Thread類的sleep()和yield ()方法是靜態的?

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

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

可以用join方法實現。

43、你需要實現一個高效的緩存,它允許多個用戶讀,但只允許一個用戶寫,以此來保持它的完整性,你會怎樣去實現它?

volatile關鍵字,讀寫鎖,寫時複製等等都可以實現。

44、用Java寫代碼來解決生產者——消費者問題。

阻塞隊列實現即可,也可以用wait和notify來解決這個問題,或者用Semaphore

 45、Java中如何停止一個線程?

使用共享變量的方式

在這種方式中,之所以引入共享變量,是因爲該變量可以被多個執行相同任務的線程用來作爲是否中斷的信號,通知中斷線程的執行。

使用interrupt方法終止線程

如果一個線程由於等待某些事件的發生而被阻塞,又該怎樣停止該線程呢?比如當一個線程由於需要等候鍵盤輸入而被阻塞,或者調用Thread.join()方法,或者Thread.sleep()方法,在網絡中調用ServerSocket.accept()方法,或者調用了DatagramSocket.receive()方法時,都有可能導致線程阻塞,使線程處於處於不可運行狀態時,即使主程序中將該線程的共享變量設置爲true,但該線程此時根本無法檢查循環標誌,當然也就無法立即中斷。所以應該儘量使用Thread提供的interrupt()方法,因爲該方法雖然不會中斷一個正在運行的線程,但是它可以使一個被阻塞的線程拋出一箇中斷異常,從而使線程提前結束阻塞狀態。

46、JVM中哪個參數是用來控制線程的棧堆棧大小的

-Xss

47、如果同步塊內的線程拋出異常鎖會釋放嗎?

48、單例模式的雙重檢查實現是什麼?爲什麼並不安全?如何在Java中創建線程安全的Singleton?

實現參見cn.enjoyedu.ch7.dcl. SingleDcl,不安全的根本原因是重排序會導致未初始化完成的對象可以被其他線程看見而導致錯誤。創建安全的單例模式有:延遲佔位模式、在聲明的時候就new這個類的實例、枚舉

49、寫出3條你遵循的多線程最佳實踐

給你的線程起個有意義的名字。 這樣可以方便找bug或追蹤。OrderProcessor, QuoteProcessor or TradeProcessor 這種名字比 Thread-1. Thread-2 and Thread-3 好多了,給線程起一個和它要完成的任務相關的名字,所有的主要框架甚至JDK都遵循這個最佳實踐。

避免鎖定和縮小同步的範圍 鎖花費的代價高昂且上下文切換更耗費時間空間,試試最低限度的使用同步和鎖,縮小臨界區。因此相對於同步方法我更喜歡同步塊,它給我擁有對鎖的絕對控制權。

多用同步類少用wait 和 notify 首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 這些同步類簡化了編碼操作,而用wait和notify很難實現對複雜控制流的控制。其次,這些類是由最好的企業編寫和維護在後續的JDK中它們還會不斷優化和完善,使用這些更高等級的同步工具你的程序可以不費吹灰之力獲得優化。

多用併發集合少用同步集合 這是另外一個容易遵循且受益巨大的最佳實踐,併發集合比同步集合的可擴展性更好,所以在併發編程時使用併發集合效果更好。

比如併發編程的黃金原則,儘量無鎖化編程等等……..

50、請概述線程池的創建參數,怎麼樣合理配置一個線程池的參數?

參見併發編程專題第一章

51、請概述鎖的公平和非公平,JDK內部是如何實現的。

公平鎖是指所有試圖獲得鎖的線程按照獲取鎖的順序依次獲得鎖,而非公平鎖則是當前的鎖狀態沒有被佔用時,當前線程可以直接佔用,而不需要等待。在實現上,非公平鎖邏輯基本跟公平鎖一致,唯一的區別是,當前線程不需要判斷同步隊列中是否有等待線程。

非公平鎖性能高於公平鎖性能。首先,在恢復一個被掛起的線程與該線程真正運行之間存在着嚴重的延遲。而且,非公平鎖能更充分的利用cpu的時間片,儘量的減少cpu空閒的狀態時間。

使用場景的話呢,其實還是和他們的屬性一一相關,比如:如果業務中線程佔用(處理)時間要遠長於線程等待,那用非公平鎖其實效率並不明顯,但是用公平鎖可以保證不會有線程被餓死。

52、請概述AQS

是用來構建鎖或者其他同步組件的基礎框架,比如ReentrantLock、ReentrantReadWriteLock和CountDownLatch就是基於AQS實現的。它使用了一個int成員變量表示同步狀態,通過內置的FIFO隊列來完成資源獲取線程的排隊工作。它是CLH隊列鎖的一種變體實現。它可以實現2種同步方式:獨佔式,共享式。

AQS的主要使用方式是繼承,子類通過繼承AQS並實現它的抽象方法來管理同步狀態,同步器的設計基於模板方法模式,所以如果要實現我們自己的同步工具類就需要覆蓋其中幾個可重寫的方法,如tryAcquire、tryReleaseShared等等。

這樣設計的目的是同步組件(比如鎖)是面向使用者的,它定義了使用者與同步組件交互的接口(比如可以允許兩個線程並行訪問),隱藏了實現細節;同步器面向的是鎖的實現者,它簡化了鎖的實現方式,屏蔽了同步狀態管理、線程的排隊、等待與喚醒等底層操作。這樣就很好地隔離了使用者和實現者所需關注的領域。

在內部,AQS維護一個共享資源state,通過內置的FIFO來完成獲取資源線程的排隊工作。該隊列由一個一個的Node結點組成,每個Node結點維護一個prev引用和next引用,分別指向自己的前驅和後繼結點,構成一個雙端雙向鏈表。

同時與Condition相關的等待隊列,節點類型也是Node,構成一個單向鏈表。

53、請概述volatile

volatile關鍵字的作用主要有兩點:

多線程主要圍繞可見性和原子性兩個特性而展開,使用volatile關鍵字修飾的變量,保證了其在多線程之間的可見性,即每次讀取到volatile變量,一定是最新的數據。但是volatile不能保證操作的原子,對任意單個volatile變量的讀/寫具有原子性,但類似於++這種複合操作不具有原子性。。

代碼底層在執行時爲了獲取更好的性能會對指令進行重排序,多線程下可能會出現一些意想不到的問題。使用volatile則會對禁止重排序,當然這也一定程度上降低了代碼執行效率。

同時在內存語義上,當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量值刷新到主內存,當讀一個volatile變量時,JMM會把該線程對應的本地內存置爲無效。線程接下來將從主內存中讀取共享變量。

在Java中對於volatile修飾的變量,編譯器在生成字節碼時,會在指令序列中插入內存屏障來禁止特定類型的處理器重排序問題、強制刷新和讀取。

在具體實現上,volatile關鍵字修飾的變量會存在一個“lock:”的前綴。它不是一種內存屏障,但是它能完成類似內存屏障的功能。Lock會對CPU總線和高速緩存加鎖,可以理解爲CPU指令級的一種鎖。

同時該指令會將當前處理器緩存行的數據直接寫會到系統內存中,且這個寫回內存的操作會使在其他CPU裏緩存了該地址的數據無效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章