Java并发面试宝典,并发相关面试再也难不倒你!
1、在java中守護線程和用戶線程的區(qū)別?
java中的線程分為兩種:守護線程(Daemon)和用戶線程(User)。
任何線程都可以設(shè)置為守護線程和用戶線程,通過方法Thread.setDaemon(bool on);true則把該線程設(shè)置為守護線程,反之則為用戶線程。Thread.setDaemon()必須在Thread.start()之前調(diào)用,否則運行時會拋出異常。
兩者的區(qū)別:
唯一的區(qū)別是判斷虛擬機(JVM)何時離開,Daemon是為其他線程提供服務(wù),如果全部的User Thread已經(jīng)結(jié)束,Daemon 沒有可服務(wù)的線程,JVM關(guān)閉。
擴展:Thread Dump打印出來的線程信息,含有daemon字樣的線程即為守護進程
2、線程與進程的區(qū)別?
進程是操作系統(tǒng)分配資源的最小單元,線程是操作系統(tǒng)調(diào)度的最小單元。一個程序至少有一個進程,一個進程至少有一個線程。
3、什么是多線程中的上下文切換
多線程會共同使用一組計算機上的CPU,而線程數(shù)大于給程序分配的CPU數(shù)量時,為了讓各個線程都有執(zhí)行的機會,就需要輪轉(zhuǎn)使用CPU。不同的線程切換使用CPU發(fā)生的切換數(shù)據(jù)等就是上下文切換。
4、死鎖和活鎖的區(qū)別
死鎖:是指兩個或兩個以上的進程(或線程)在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用,它們都將無法推進下去。
產(chǎn)生死鎖的必要條件:
互斥條件:所謂互斥就是進程在某一時間內(nèi)獨占資源。
請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
不剝奪條件進程已獲得資源,在末使用完之前,不能強行剝奪。
循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
活鎖:任務(wù)或者執(zhí)行者沒有被阻塞,由于某些條件沒有滿足,導致一直重復嘗試,失敗,嘗試,失敗。
活鎖和死鎖的區(qū)別在于,處于活鎖的實體是在不斷的改變狀態(tài),所謂的“活”, 而處于死鎖的實體表現(xiàn)為等待,活鎖有可能自行解開,死鎖則不能。
饑餓:一個或者多個線程因為種種原因無法獲得所需要的資源,導致一直無法執(zhí)行的狀態(tài)。
5、synchronized底層實現(xiàn)原理?
synchronized (this)原理:涉及兩條指令:monitorenter,monitorexit;再說同步方法,從同步方法反編譯的結(jié)果來看,方法的同步并沒有通過指令monitorenter和monitorexit來實現(xiàn),相對于普通方法,其常量池中多了ACC_SYNCHRONIZED標示符。JVM就是根據(jù)該標示符來實現(xiàn)方法的同步的:當方法被調(diào)用時,調(diào)用指令將會檢查方法的 ACC_SYNCHRONIZED 訪問標志是否被設(shè)置,如果設(shè)置了,執(zhí)行線程將先獲取monitor,獲取成功之后才能執(zhí)行方法體,方法執(zhí)行完后再釋放monitor。在方法執(zhí)行期間,其他任何線程都無法再獲得同一個monitor對象。
注意,這個問題可能會接著追問,java對象頭信息,偏向鎖,輕量鎖,重量級鎖及其他們相互間轉(zhuǎn)化。
6、什么是線程組,為什么在java中不推薦使用?
ThreadGroup類,可以把線程歸屬到某一個線程組中,線程組中可以有線程對象,也可以有線程組,組中還可以有線程,這樣的組織結(jié)構(gòu)有點類似于樹的形式。
1)線程組ThreadGroup對象中比較有用的方法是stop、resume、suspend等,由于這幾個方法會導致線程的安全問題(主要是死鎖問題),已經(jīng)被官方廢棄掉了,所以線程組本身的應(yīng)用價值就大打折扣了。
2)線程組ThreadGroup不是線程安全的,這在使用過程中獲取的信息并不全是及時有效的,這就降低了它的統(tǒng)計使用價值。
7、什么是Executor框架?為什么使用Executor框架?
Executor框架是一個根據(jù)一組執(zhí)行策略調(diào)用,調(diào)度,執(zhí)行和控制的異步任務(wù)的框架。
每次執(zhí)行任務(wù)創(chuàng)建線程 new Thread()比較消耗性能,創(chuàng)建一個線程是比較耗時、耗資源的。
調(diào)用 new Thread()創(chuàng)建的線程缺乏管理,而且可以無限制的創(chuàng)建,線程之間的相互競爭會導致過多占用系統(tǒng)資源而導致系統(tǒng)癱瘓,還有線程之間的頻繁交替也會消耗很多系統(tǒng)資源。接使用new Thread() 啟動的線程不利于擴展,比如定時執(zhí)行、定期執(zhí)行、定時定期執(zhí)行、線程中斷等都不便實現(xiàn)。
8、在Java中Executor和Executors的區(qū)別?
Executors 工具類的不同方法按照我們的需求創(chuàng)建了不同的線程池,來滿足業(yè)務(wù)的需求。
Executor 接口對象能執(zhí)行我們的線程任務(wù)。
ExecutorService接口繼承了Executor接口并進行了擴展,提供了更多的方法我們能獲得任務(wù)執(zhí)行的狀態(tài)并且可以獲取任務(wù)的返回值。
使用ThreadPoolExecutor 可以創(chuàng)建自定義線程池。
9、什么是原子操作?在Java concurrencyAPI中有哪些原子類?
原子操作(atomic operation)意為”不可被中斷的一個或一系列操作” 。
處理器使用基于對緩存加鎖或總線加鎖的方式來實現(xiàn)多處理器之間的原子操作。
在Java中可以通過鎖和循環(huán)CAS的方式來實現(xiàn)原子操作。 CAS操作——Compare & Set,或是 Compare & Swap,現(xiàn)在幾乎所有的CPU指令都支持CAS的原子操作。java.util.concurrent.atomic下提供了大量的原子操作類,比如原子類:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference ,原子數(shù)組:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray ,原子屬性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
10、Java concurrency API 中的lock接口是什么?對比synchronized它有什么優(yōu)勢?
Lock接口比同步方法和同步塊提供了更具擴展性的鎖操作。
他們允許更靈活的結(jié)構(gòu),可以具有完全不同的性質(zhì),并且可以支持多個相關(guān)類的條件對象。
它的優(yōu)勢有:可以使鎖更公平,可以使線程在等待鎖的時候響應(yīng)中斷,可以讓線程嘗試獲取鎖,并在無法獲取鎖的時候立即返回或者等待一段時間,可以在不同的范圍,以不同的順序獲取和釋放鎖。
整體上來說Lock是synchronized的擴展版,Lock提供了無條件的、可輪詢的(tryLock方法)、定時的(tryLock帶參方法)、可中斷的(lockInterruptibly)、可多條件隊列的(newCondition方法)鎖操作。另外Lock的實現(xiàn)類基本都支持非公平鎖(默認)和公平鎖,synchronized只支持非公平鎖,當然,在大部分情況下,非公平鎖是高效的選擇。
11、什么是阻塞隊列?阻塞隊列的原理是什么?如何使用阻塞隊列來實現(xiàn)生產(chǎn)者-消費者模型?
阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作是:在隊列為空時,獲取元素的線程會等待隊列變?yōu)榉强铡.旉犃袧M時,存儲元素的線程會等待隊列可用。
阻塞隊列常用于生產(chǎn)者和消費者的場景,生產(chǎn)者是往隊列里添加元素的線程,消費者是從隊列里拿元素的線程。阻塞隊列就是生產(chǎn)者存放元素的容器,而消費者也只從容器里拿元素。JDK7提供了7個阻塞隊列。在實現(xiàn)上,主要是利用了Condition和Lock的等待通知模式。
12、什么是callable和future?
Callable接口類似于Runnable,從名字就可以看出來了,但是Runnable不會返回結(jié)果,并且無法拋出返回結(jié)果的異常,而Callable功能更強大一些,被線程執(zhí)行后,可以返回值,這個返回值可以被Future拿到,也就是說,Future可以拿到異步執(zhí)行任務(wù)的返回值。可以認為是帶有回調(diào)的Runnable。Future接口表示異步任務(wù),是還沒有完成的任務(wù)給出的未來結(jié)果。所以說Callable用于產(chǎn)生結(jié)果,Future用于獲取結(jié)果。
13、什么是future task?
在Java并發(fā)程序中FutureTask表示一個可以取消的異步運算。它有啟動和取消運算、查詢運算是否完成和取回運算結(jié)果等方法。只有當運算完成的時候結(jié)果才能取回,如果運算尚未完成get方法將會阻塞。一個FutureTask對象可以對調(diào)用了Callable和Runnable的對象進行包裝,由于FutureTask也是調(diào)用了Runnable接口所以它可以提交給Executor來執(zhí)行。
14、什么是并發(fā)容器的實現(xiàn)?
何為同步容器:可以簡單地理解為通過synchronized來實現(xiàn)同步的容器,如果有多個線程調(diào)用同步容器的方法,它們將會串行執(zhí)行。比如Vector,Hashtable,以及Collections.synchronizedSet,synchronizedList等方法返回的容器。
并發(fā)容器使用了與同步容器完全不同的加鎖策略來提供更高的并發(fā)性和伸縮性,例ConcurrentHashMap中采用了一種粒度更細的加鎖機制,可以稱為分段鎖,在這種鎖機制下,允許任意數(shù)量的讀線程并發(fā)地訪問map,并且執(zhí)行讀操作的線程和寫操作的線程也可以并發(fā)的訪問map,同時允許一定數(shù)量的寫操作線程并發(fā)地修改map,所以它可以在并發(fā)環(huán)境下實現(xiàn)更高的吞吐量。
15、多線程同步和互斥有幾種實現(xiàn)方法,都是什么?
線程同步是指線程之間所具有的一種制約關(guān)系,一個線程的執(zhí)行依賴另一個線程的消息,當它沒有得到另一個線程的消息時應(yīng)等待,直到消息到達時才被喚醒。
線程互斥是指對于共享的進程系統(tǒng)資源,在各單個線程訪問時的排它性。當有若干個線程都要使用某一共享資源時,任何時刻最多只允許一個線程去使用,其它要使用該資源的線程必須等待,直到占用資源者釋放該資源。線程互斥可以看成是一種特殊的線程同步。
線程間的同步方法大體可分為兩類:用戶模式和內(nèi)核模式。顧名思義,內(nèi)核模式就是指利用系統(tǒng)內(nèi)核對象的單一性來進行同步,使用時需要切換內(nèi)核態(tài)與用戶態(tài),而用戶模式就是不需要切換到內(nèi)核態(tài),只在用戶態(tài)完成操作。
用戶模式下的方法有:原子操作(例如一個單一的全局變量),臨界區(qū)。內(nèi)核模式下的方法有:事件,信號量,互斥量。
16、什么是競爭條件?
當多個進程都企圖對共享數(shù)據(jù)進行某種處理,而最后的結(jié)果又取決于進程運行的順序時,則我們認為這發(fā)生了競爭條件(race condition)。
17、為什么我們調(diào)用start()方法,為什么我們不能直接調(diào)用run()方法?
當你調(diào)用start()方法時你將創(chuàng)建新的線程,并且執(zhí)行在run()方法里的代碼。 但是如果你直接調(diào)用run()方法,它不會創(chuàng)建新的線程也不會執(zhí)行調(diào)用線程的代碼,只會把run方法當作普通方法去執(zhí)行。
18、在Java中CycliBarriar和CountdownLatch有什么區(qū)別?
CyclicBarrier可以重復使用,而CountdownLatch不能重復使用。
19、什么是不可變對象,它對寫并發(fā)應(yīng)用有什么幫助?
不可變對象(Immutable Objects)即對象一旦被創(chuàng)建它的狀態(tài)(對象的數(shù)據(jù),也即對象屬性值)就不能改變,反之即為可變對象(Mutable Objects)。
不可變對象的類即為不可變類(Immutable Class),Java平臺類庫中包含許多不可變類,如String、基本類型的包裝類、BigInteger和BigDecimal等。 不可變對象天生是線程安全的。它們的常量(域)是在構(gòu)造函數(shù)中創(chuàng)建的。既然它們的狀態(tài)無法修改,這些常量永遠不會變。
不可變對象永遠是線程安全的。 只有滿足如下狀態(tài),一個對象才是不可變的;
①它的狀態(tài)不能在創(chuàng)建后再被修改; ②所有域都是final類型;并且它被正確創(chuàng)建。
20、notify()和notifyAll()有什么區(qū)別?
當一個線程進入wait之后,就必須等其他線程notify/notifyall,使用notifyall,可以喚醒所有處于wait狀態(tài)的線程,使其重新進入鎖的爭奪隊列中,而notify只能喚醒一個。如果沒把握,建議notifyAll,防止notigy因為信號丟失而造成程序異常。
21、什么是可重入鎖(ReentrantLock)?談?wù)勊膶崿F(xiàn)。
線程可以重復進入任何一個它已經(jīng)擁有的鎖所同步著的代碼塊,synchronized、ReentrantLock都是可重入的鎖。在實現(xiàn)上,就是線程每次獲取鎖時判定如果獲得鎖的線程是它自己時,簡單將計數(shù)器累積即可,每 釋放一次鎖,進行計數(shù)器累減,直到計算器歸零,表示線程已經(jīng)徹底釋放鎖。
22、當一個線程進入某個對象的一個synchronized的實例方法后,其它線程是否可進入此對象的其它方法?
如果其他方法沒有synchronized的話,其他線程是可以進入的。所以要開放一個線程安全的對象時,得保證每個方法都是線程安全的。
23、樂觀鎖和悲觀鎖的理解及如何實現(xiàn),有哪些實現(xiàn)方式?
悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時候都認為別人會修改,所以每次在拿數(shù)據(jù)的時候都會上鎖,這樣別人想拿這個數(shù)據(jù)就會阻塞直到它拿到鎖。Java里面的同步原語synchronized關(guān)鍵字的實現(xiàn)是悲觀鎖。
樂觀鎖:顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數(shù)據(jù),可以使用版本號等機制。在Java中j原子變量類就是使用了樂觀鎖的一種實現(xiàn)方式CAS實現(xiàn)的。
樂觀鎖的實現(xiàn)方式: 使用版本標識來確定讀到的數(shù)據(jù)與提交時的數(shù)據(jù)是否一致。提交后修改版本標識,不一致時可以采取丟棄和再次嘗試的策略。
java中的Compare and Swap即CAS ,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試。
24、什么是CAS操作,缺點是什么?
CAS的基本思路就是,如果這個地址上的值和期望的值相等,則給其賦予新值,否則不做任何事兒,但是要返回原值是多少。每一個CAS操作過程都包含三個運算符:一個內(nèi)存地址V,一個期望的值A(chǔ)和一個新值B,操作的時候如果這個地址上存放的值等于這個期望的值A(chǔ),則將地址上的值賦為新值B,否則不做任何操作。
CAS缺點:
ABA問題:比如說一個線程one從內(nèi)存位置V中取出A,這時候另一個線程two也從內(nèi)存中取出A,并且two進行了一些操作變成了B,然后two又將V位置的數(shù)據(jù)變成A,這時候線程one進行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后one操作成功。盡管線程one的CAS操作成功,但可能存在潛藏的問題。從Java1.5開始JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。
循環(huán)時間長開銷大: 對于資源競爭嚴重(線程沖突嚴重)的情況,CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低于synchronized。
只能保證一個共享變量的原子操作: 當對一個共享變量執(zhí)行操作時,我們可以使用循環(huán)CAS的方式來保證原子操作,但是對多個共享變量操作時,循環(huán)CAS就無法保證操作的原子性,這個時候就可以用鎖。
25、SynchronizedMap和ConcurrentHashMap有什么區(qū)別?
SynchronizedMap一次鎖住整張表來保證線程安全,所以每次只能有一個線程來訪為map。
ConcurrentHashMap使用分段鎖來保證在多線程下的性能。
26、寫時復制容器可以用于什么應(yīng)用場景?
CopyOnWrite并發(fā)容器用于對于絕大部分訪問都是讀,且只是偶爾寫的并發(fā)場景。比如白名單,黑名單,商品類目的訪問和更新場景。
透露的思想:讀寫分離,讀和寫分開。
最終一致性:使用另外開辟空間的思路,來解決并發(fā)沖突
27、volatile有什么用?能否用一句話說明下volatile的應(yīng)用場景?
volatile保證內(nèi)存可見性和禁止指令重排。
volatile用于多線程環(huán)境下的一寫多讀,或者無關(guān)聯(lián)的多寫。
28、為什么代碼會重排序?
在執(zhí)行程序時,為了提供性能,處理器和編譯器常常會對指令進行重排序,但是不能隨意重排序,不是你想怎么排序就怎么排序,它需要滿足以下兩個條件:
在單線程環(huán)境下不能改變程序運行的結(jié)果;
存在數(shù)據(jù)依賴關(guān)系的不允許重排序
29、在java中wait和sleep方法的不同?
最大的不同是在等待時wait會釋放鎖,而sleep一直持有鎖。Wait通常被用于線程間交互,sleep通常被用于暫停執(zhí)行。
30、一個線程運行時發(fā)生異常會怎樣?
如果異常沒有被捕獲該線程將會停止執(zhí)行。hread.UncaughtExceptionHandler是用于處理未捕獲異常造成線程突然中斷情況的一個內(nèi)嵌接口。當一個未捕獲異常將造成線程中斷的時候JVM會使用Thread.getUncaughtExceptionHandler()來查詢線程的UncaughtExceptionHandler并將線程和異常作為參數(shù)傳遞給handler的uncaughtException()方法進行處理。
31、為什么wait, notify 和 notifyAll這些方法不在thread類里面?
JAVA提供的鎖是對象級的而不是線程級的,每個對象都有鎖,通過線程獲得。如果線程需要等待某些鎖那么調(diào)用對象中的wait()方法就有意義了。如果wait()方法定義在Thread類中,線程正在等待的是哪個鎖就不明顯了。簡單的說,由于wait,notify和notifyAll都是鎖級別的操作,所以把他們定義在Object類中因為鎖屬于對象。
32、什么是ThreadLocal變量?
ThreadLocal是Java里一種特殊的變量。每個線程都有一個ThreadLocal就是每個線程都擁有了自己獨立的一個變量,競爭條件被徹底消除了。
33、Java中interrupted 和 isInterrupted方法的區(qū)別?
interrupted() 和 isInterrupted()的主要區(qū)別是前者會將中斷狀態(tài)清除而后者不會。Java多線程的中斷機制是用內(nèi)部標識來實現(xiàn)的,調(diào)用Thread.interrupt()來中斷一個線程就會設(shè)置中斷標識為true。當中斷線程調(diào)用靜態(tài)方法Thread.interrupted()來檢查中斷狀態(tài)時,中斷狀態(tài)會被清零。而非靜態(tài)方法isInterrupted()用來查詢其它線程的中斷狀態(tài)且不會改變中斷狀態(tài)標識。
34、為什么wait和notify方法要在同步塊中調(diào)用?
主要是因為Java API強制要求這樣做,如果你不這么做,你的代碼會拋出IllegalMonitorStateException異常。
35、為什么你應(yīng)該在循環(huán)中檢查等待條件?
處于等待狀態(tài)的線程可能會收到錯誤警報和偽喚醒,如果不在循環(huán)中檢查等待條件,程序就會在沒有滿足結(jié)束條件的情況下退出。因此,當一個等待線程醒來時,不能認為它原來的等待狀態(tài)仍然是有效的,在notify()方法調(diào)用之后和等待線程醒來之前這段時間它可能會改變。這就是在循環(huán)中使用wait()方法效果更好的原因
36、怎么檢測一個線程是否擁有鎖?
在java.lang.Thread中有一個方法叫holdsLock(),它返回true如果當且僅當當前線程擁有某個具體對象的鎖。
37、你如何在Java中獲取線程堆棧?
kill -3 [java pid] :不會在當前終端輸出,它會輸出到代碼執(zhí)行的或指定的地方去。比如,kill -3 tomcat pid, 輸出堆棧到log目錄下。
Jstack [java pid] :這個比較簡單,在當前終端顯示,也可以重定向到指定文件中。或者使用Java提供的擬機線程系統(tǒng)的管理接口ManagementFactory.getThreadMXBean()。
38、Java線程池中submit() 和 execute()方法有什么區(qū)別?
兩個方法都可以向線程池提交任務(wù),execute()方法的返回類型是void,它定義在Executor接口中。
而submit()方法可以返回持有計算結(jié)果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口
39、你對線程優(yōu)先級的理解是什么?
每一個線程都是有優(yōu)先級的,一般來說,高優(yōu)先級的線程在運行時會具有優(yōu)先權(quán),但這依賴于線程調(diào)度的實現(xiàn),這個實現(xiàn)是和操作系統(tǒng)相關(guān)的(OS dependent)。我們可以定義線程的優(yōu)先級,但是這并不能保證高優(yōu)先級的線程會在低優(yōu)先級的線程前執(zhí)行。線程優(yōu)先級是一個int變量(從1-10),1代表最低優(yōu)先級,10代表最高優(yōu)先級。
java的線程優(yōu)先級調(diào)度會委托給操作系統(tǒng)去處理,所以與具體的操作系統(tǒng)優(yōu)先級有關(guān),如非特別需要,一般無需設(shè)置線程優(yōu)先級。
40、你如何確保main()方法所在的線程是Java 程序最后結(jié)束的線程?
可以使用Thread類的join()方法(或者CountDownLatch工具類)來確保所有程序創(chuàng)建的線程在main()方法退出前結(jié)束。
41、為什么Thread類的sleep()和yield ()方法是靜態(tài)的?
Thread類的sleep()和yield()方法將在當前正在執(zhí)行的線程上運行。所以在其他處于等待狀態(tài)的線程上調(diào)用這些方法是沒有意義的。這就是為什么這些方法是靜態(tài)的。它們可以在當前正在執(zhí)行的線程中工作,并避免程序員錯誤的認為可以在其他非運行線程調(diào)用這些方法。
42、現(xiàn)在有T1、T2、T3三個線程,你怎樣保證T2在T1執(zhí)行完后執(zhí)行,T3在T2執(zhí)行完后執(zhí)行?
可以用join方法實現(xiàn)。
43、你需要實現(xiàn)一個高效的緩存,它允許多個用戶讀,但只允許一個用戶寫,以此來保持它的完整性,你會怎樣去實現(xiàn)它?
volatile關(guān)鍵字,讀寫鎖,寫時復制等等都可以實現(xiàn)。
用Java實現(xiàn)阻塞隊列,見作業(yè)答案:包cn.enjoyedu.ch5.answer下用Java寫代碼來解決生產(chǎn)者——消費者問題。阻塞隊列實現(xiàn)即可,也可以用wait和notify來解決這個問題,或者用Semaphore。
44、用Java編程一個會導致死鎖的程序,你將怎么解決?
參見代碼cn.enjoyedu.ch7. NormalDeadLock,如何解決死鎖,參見筆記。
45、Java中如何停止一個線程?
使用共享變量的方式:在這種方式中,之所以引入共享變量,是因為該變量可以被多個執(zhí)行相同任務(wù)的線程用來作為是否中斷的信號,通知中斷線程的執(zhí)行。
使用interrupt方法終止線程:如果一個線程由于等待某些事件的發(fā)生而被阻塞,又該怎樣停止該線程呢?比如當一個線程由于需要等候鍵盤輸入而被阻塞,或者調(diào)用Thread.join()方法,或者Thread.sleep()方法,在網(wǎng)絡(luò)中調(diào)用ServerSocket.accept()方法,或者調(diào)用了DatagramSocket.receive()方法時,都有可能導致線程阻塞,使線程處于處于不可運行狀態(tài)時,即使主程序中將該線程的共享變量設(shè)置為true,但該線程此時根本無法檢查循環(huán)標志,當然也就無法立即中斷。所以應(yīng)該盡量使用Thread提供的interrupt()方法,因為該方法雖然不會中斷一個正在運行的線程,但是它可以使一個被阻塞的線程拋出一個中斷異常,從而使線程提前結(jié)束阻塞狀態(tài)。
46、JVM中哪個參數(shù)是用來控制線程的棧堆棧大小的
答:-Xss
47、如果同步塊內(nèi)的線程拋出異常鎖會釋放嗎?
會
48、單例模式的雙重檢查實現(xiàn)是什么?為什么并不安全?如何在Java中創(chuàng)建線程安全的Singleton?
實現(xiàn)參見cn.enjoyedu.ch7.dcl. SingleDcl,不安全的根本原因是重排序會導致未初始化完成的對象可以被其他線程看見而導致錯誤。創(chuàng)建安全的單例模式有:延遲占位模式、在聲明的時候就new這個類的實例、枚舉
49、寫出3條你遵循的多線程最佳實踐
給你的線程起個有意義的名字。 這樣可以方便找bug或追蹤。OrderProcessor, QuoteProcessor or TradeProcessor 這種名字比 Thread-1. Thread-2 and Thread-3 好多了,給線程起一個和它要完成的任務(wù)相關(guān)的名字,所有的主要框架甚至JDK都遵循這個最佳實踐。
避免鎖定和縮小同步的范圍 鎖花費的代價高昂且上下文切換更耗費時間空間,試試最低限度的使用同步和鎖,縮小臨界區(qū)。因此相對于同步方法我更喜歡同步塊,它給我擁有對鎖的絕對控制權(quán)。
多用同步類少用wait 和 notify 首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 這些同步類簡化了編碼操作,而用wait和notify很難實現(xiàn)對復雜控制流的控制。其次,這些類是由最好的企業(yè)編寫和維護在后續(xù)的JDK中它們還會不斷優(yōu)化和完善,使用這些更高等級的同步工具你的程序可以不費吹灰之力獲得優(yōu)化。
多用并發(fā)集合少用同步集合 這是另外一個容易遵循且受益巨大的最佳實踐,并發(fā)集合比同步集合的可擴展性更好,所以在并發(fā)編程時使用并發(fā)集合效果更好。
比如并發(fā)編程的黃金原則,盡量無鎖化編程等等………
50、請概述線程池的創(chuàng)建參數(shù),怎么樣合理配置一個線程池的參數(shù)?
參見博客中線程池的內(nèi)容
https://blog.csdn.net/qq_43842093/article/details/120804660
51、請概述鎖的公平和非公平,JDK內(nèi)部是如何實現(xiàn)的。
公平鎖是指所有試圖獲得鎖的線程按照獲取鎖的順序依次獲得鎖,而非公平鎖則是當前的鎖狀態(tài)沒有被占用時,當前線程可以直接占用,而不需要等待。在實現(xiàn)上,非公平鎖邏輯基本跟公平鎖一致,唯一的區(qū)別是,當前線程不需要判斷同步隊列中是否有等待線程。
非公平鎖性能高于公平鎖性能。首先,在恢復一個被掛起的線程與該線程真正運行之間存在著嚴重的延遲。而且,非公平鎖能更充分的利用cpu的時間片,盡量的減少cpu空閑的狀態(tài)時間。
使用場景的話呢,其實還是和他們的屬性一一相關(guān),比如:如果業(yè)務(wù)中線程占用(處理)時間要遠長于線程等待,那用非公平鎖其實效率并不明顯,但是用公平鎖可以保證不會有線程被餓死。
52、請概述AQS
是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架,比如ReentrantLock、ReentrantReadWriteLock和CountDownLatch就是基于AQS實現(xiàn)的。它使用了一個int成員變量表示同步狀態(tài),通過內(nèi)置的FIFO隊列來完成資源獲取線程的排隊工作。它是CLH隊列鎖的一種變體實現(xiàn)。它可以實現(xiàn)2種同步方式:獨占式,共享式。
AQS的主要使用方式是繼承,子類通過繼承AQS并實現(xiàn)它的抽象方法來管理同步狀態(tài),同步器的設(shè)計基于模板方法模式,所以如果要實現(xiàn)我們自己的同步工具類就需要覆蓋其中幾個可重寫的方法,如tryAcquire、tryReleaseShared等等。
這樣設(shè)計的目的是同步組件(比如鎖)是面向使用者的,它定義了使用者與同步組件交互的接口(比如可以允許兩個線程并行訪問),隱藏了實現(xiàn)細節(jié);同步器面向的是鎖的實現(xiàn)者,它簡化了鎖的實現(xiàn)方式,屏蔽了同步狀態(tài)管理、線程的排隊、等待與喚醒等底層操作。這樣就很好地隔離了使用者和實現(xiàn)者所需關(guān)注的領(lǐng)域。
在內(nèi)部,AQS維護一個共享資源state,通過內(nèi)置的FIFO來完成獲取資源線程的排隊工作。該隊列由一個一個的Node結(jié)點組成,每個Node結(jié)點維護一個prev引用和next引用,分別指向自己的前驅(qū)和后繼結(jié)點,構(gòu)成一個雙端雙向鏈表。
同時與Condition相關(guān)的等待隊列,節(jié)點類型也是Node,構(gòu)成一個單向鏈表。
53、請概述volatile
volatile關(guān)鍵字的作用主要有兩點:
多線程主要圍繞可見性和原子性兩個特性而展開,使用volatile關(guān)鍵字修飾的變量,保證了其在多線程之間的可見性,即每次讀取到volatile變量,一定是最新的數(shù)據(jù)。但是volatile不能保證操作的原子,對任意單個volatile變量的讀/寫具有原子性,但類似于++這種復合操作不具有原子性。。
代碼底層在執(zhí)行時為了獲取更好的性能會對指令進行重排序,多線程下可能會出現(xiàn)一些意想不到的問題。使用volatile則會對禁止重排序,當然這也一定程度上降低了代碼執(zhí)行效率。
同時在內(nèi)存語義上,當寫一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存中的共享變量值刷新到主內(nèi)存,當讀一個volatile變量時,JMM會把該線程對應(yīng)的本地內(nèi)存置為無效。線程接下來將從主內(nèi)存中讀取共享變量。
在Java中對于volatile修飾的變量,編譯器在生成字節(jié)碼時,會在指令序列中插入內(nèi)存屏障來禁止特定類型的處理器重排序問題、強制刷新和讀取。
在具體實現(xiàn)上,volatile關(guān)鍵字修飾的變量會存在一個“l(fā)ock:”的前綴。它不是一種內(nèi)存屏障,但是它能完成類似內(nèi)存屏障的功能。Lock會對CPU總線和高速緩存加鎖,可以理解為CPU指令級的一種鎖。
同時該指令會將當前處理器緩存行的數(shù)據(jù)直接寫會到系統(tǒng)內(nèi)存中,且這個寫回內(nèi)存的操作會使在其他CPU里緩存了該地址的數(shù)據(jù)無效。
總結(jié)
以上是生活随笔為你收集整理的Java并发面试宝典,并发相关面试再也难不倒你!的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL中TEXT数据类型的最大长度_
- 下一篇: 工业用微型计算机(11)-指令系统(8)