溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java線程面試題有哪些

發(fā)布時(shí)間:2021-11-30 14:50:25 來(lái)源:億速云 閱讀:235 作者:iii 欄目:大數(shù)據(jù)

本篇內(nèi)容主要講解“Java線程面試題有哪些”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Java線程面試題有哪些”吧!

Java 關(guān)鍵字volatile 與 synchronized 作用與區(qū)別?

JVM中存在一個(gè)主存區(qū)(Main Memory或Java Heap Memory),Java中所有變量都是存在主存中的,對(duì)于所有線程進(jìn)行共享,

而每個(gè)線程又存在自己的工作內(nèi)存(Working Memory),工作內(nèi)存中保存的是主存中某些變量的拷貝,

線程對(duì)所有變量的操作并非發(fā)生在主存區(qū),

而是發(fā)生在工作內(nèi)存中,而線程之間是不能直接相互訪問(wèn),變量在程序中的傳遞,是依賴主存來(lái)完成的。

在java中可以實(shí)現(xiàn)可見(jiàn)性的兩個(gè)關(guān)鍵字 

 1)使用關(guān)鍵字synchronized

 2)使用關(guān)鍵字volatile

 Synchronized能夠?qū)崿F(xiàn)多線程的原子性(同步)和可見(jiàn)性。

JVM關(guān)于Synchronized的兩條規(guī)定:

1)線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存中。

2)線程加鎖時(shí),將清空工作內(nèi)存中共享變量的值,從而使用共享變量時(shí)需要從主內(nèi)存中重新讀取最新的值(注意:加鎖和解鎖需要同一把鎖)。

volatile可以保證變量的可見(jiàn)性,但是不能保證復(fù)合操作的原子性

volatile如何實(shí)現(xiàn)內(nèi)存可見(jiàn)性?

深入來(lái)說(shuō):通過(guò)加入內(nèi)存屏障和禁止重排序優(yōu)化來(lái)實(shí)現(xiàn)的。

1)對(duì)volatile變量執(zhí)行寫操作時(shí),會(huì)在寫操作后加入一條store屏障指令。

2)對(duì)volatile變量執(zhí)行讀操作時(shí),會(huì)在讀操作后加入一條load屏障指令。

通俗地講:volatile變量在每次被線程訪問(wèn)時(shí),都強(qiáng)迫從主內(nèi)存中重讀該變量的值,而當(dāng)該變量發(fā)生變化時(shí),又會(huì)強(qiáng)迫線程將最新的值刷新到主內(nèi)存,這樣任何時(shí)刻,不同的線程總能看到該變量的最新值。

線程寫volatile變量的過(guò)程:

1)改變線程工作內(nèi)存中volatile變量副本的值。

2)將改變后的副本的值從工作內(nèi)存刷新到主內(nèi)存。

線程讀volatile變量的過(guò)程:

1)從主內(nèi)存中讀取volatile變量的最新值到線程的工作內(nèi)存中。

2)從工作內(nèi)存中讀取volatile變量的副本

volatile不能保證volatile變量復(fù)合操作的原子性

對(duì)于下面的一段程序的使用volatile和synchronized

private int number = 0;              

number++;//不是原子操作                   

1讀取number的值                      

2將number的值加1                    

3寫入最新的number的值  

//加入synchronized,變?yōu)樵硬僮?nbsp;  

synchronized(thhis){ 

        number++; 

}

//變?yōu)関olatile變量,無(wú)法保證原子性

private volatile int number = 0;

volatile變量可用于提供線程安全,但是只能應(yīng)用于非常有限的一組用例:多個(gè)變量之間或者某個(gè)變量的當(dāng)前值與修改后值之間沒(méi)有約束。因此,單獨(dú)使用 volatile 還不足以實(shí)現(xiàn)計(jì)數(shù)器、互斥鎖或任何具有與多個(gè)變量相關(guān)的不變式(Invariants)的類(例如 “start <=end”)。

出于簡(jiǎn)易性或可伸縮性的考慮,您可能傾向于使用 volatile 變量而不是鎖。當(dāng)使用 volatile 變量而非鎖時(shí),某些習(xí)慣用法(idiom)更加易于編碼和閱讀。此外,volatile 變量不會(huì)像鎖那樣造成線程阻塞,因此也很少造成可伸縮性問(wèn)題。在某些情況下,如果讀操作遠(yuǎn)遠(yuǎn)大于寫操作,volatile 變量還可以提供優(yōu)于鎖的性能優(yōu)勢(shì)。

volatile適合的使用場(chǎng)景

只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時(shí)滿足下面兩個(gè)條件:

(1)對(duì)變量的寫入操作不依賴其當(dāng)前值

(2)該變量沒(méi)有包含在具有其他變量的不變式中。

第一個(gè)條件的限制使 volatile 變量不能用作線程安全計(jì)數(shù)器。雖然增量操作(x++)看上去類似一個(gè)單獨(dú)操作,實(shí)際上它是一個(gè)由(讀?。薷模瓕懭耄┎僮餍蛄薪M成的組合操作,必須以原子方式執(zhí)行,而 volatile 不能提供必須的原子特性。實(shí)現(xiàn)正確的操作需要使x 的值在操作期間保持不變,而 volatile 變量無(wú)法實(shí)現(xiàn)這點(diǎn)。(然而,如果只從單個(gè)線程寫入,那么可以忽略第一個(gè)條件。)

總結(jié):

1)volatile比synchronized更輕量級(jí)。

2)volatile沒(méi)有synchronized使用的廣泛。

3)volatile不需要加鎖,比synchronized更輕量級(jí),不會(huì)阻塞線程。

4)從內(nèi)存可見(jiàn)性角度看,volatile讀相當(dāng)于加鎖,volatile寫相當(dāng)于解鎖。

5)synchronized既能保證可見(jiàn)性,又能保證原子性,而volatile只能保證可見(jiàn)性,無(wú)法保證原子性。

6)volatile本身不保證獲取和設(shè)置操作的原子性,僅僅保持修改的可見(jiàn)性。但是java的內(nèi)存模型保證聲明為volatile的long和double變量的get和set操作是原子的。

注意:

對(duì)64位(long、double)變量的讀寫可能不是原子操作

Java內(nèi)存模型允許JVM將沒(méi)有被volatile修飾的64位數(shù)據(jù)類型的讀寫操作劃分為兩次32位的讀寫操作來(lái)運(yùn)行。

導(dǎo)致問(wèn)題:有可能會(huì)出現(xiàn)讀取到半個(gè)變量的情況。

解決方法:加volatile關(guān)鍵字。

一個(gè)問(wèn)題:即使沒(méi)有保證可見(jiàn)性的措施,很多時(shí)候共享變量依然能夠在主內(nèi)存和工作內(nèi)存間得到及時(shí)的更新?

        答:一般只有在短時(shí)間內(nèi)高并發(fā)的情況下才會(huì)出現(xiàn)變量得不到及時(shí)更新的情況,因?yàn)镃PU在執(zhí)行時(shí)會(huì)很快地刷新緩存,

所以一般情況下很難看到這種問(wèn)題。慢了不就不會(huì)刷新了。CPU運(yùn)算快的話,在分配的時(shí)間片內(nèi)就能完成所有工作:

工作內(nèi)從1->主內(nèi)存->工作內(nèi)存2,

這樣一來(lái)就保證了數(shù)據(jù)的可見(jiàn)性。在這個(gè)過(guò)程中,假如線程沒(méi)有在規(guī)定時(shí)間內(nèi)完成工作,然后這個(gè)線程就釋放CPU,分配給其它線程,

該線程就需要等待CPU下次給該線程分配時(shí)間片,如果在這段時(shí)間內(nèi)有別的線程訪問(wèn)共享變量,可見(jiàn)性就沒(méi)法保證了。

什么是ThreadLocal變量?

ThreadLocal使用場(chǎng)合主要解決多線程中數(shù)據(jù)數(shù)據(jù)因并發(fā)產(chǎn)生不一致問(wèn)題。

ThreadLocal為每個(gè)線程的中并發(fā)訪問(wèn)的數(shù)據(jù)提供一個(gè)副本,通過(guò)訪問(wèn)副本來(lái)運(yùn)行業(yè)務(wù),

這樣的結(jié)果是耗費(fèi)了內(nèi)存,單大大減少了線程同步所帶來(lái)性能消耗,也減少了線程并發(fā)控制的復(fù)雜度。

Spring使用ThreadLocal解決線程安全問(wèn)題。通常只有無(wú)狀態(tài)的Bean才可以在多線程環(huán)境下共享,

在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因?yàn)镾pring對(duì)一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全的“狀態(tài)性對(duì)象”采用ThreadLocal進(jìn)行封裝,

讓它們也成為線程安全的“狀態(tài)性對(duì)象”,

因此有狀態(tài)的Bean就能夠以singleton的方式在多線程中正常工作了。

一般的Web應(yīng)用劃分為控制層、服務(wù)層和持久層三個(gè)層次,在不同的層中編寫對(duì)應(yīng)的邏輯

,下層通過(guò)接口向上層開(kāi)放功能調(diào)用。在一般情況下,從接收請(qǐng)求到返回響應(yīng)所經(jīng)過(guò)的所有程序調(diào)用都同屬于一個(gè)線程。

這樣用戶就可以根據(jù)需要,將一些非線程安全的變量以ThreadLocal存放,

在同一次請(qǐng)求響應(yīng)的調(diào)用線程中,

所有對(duì)象所訪問(wèn)的同一ThreadLocal變量都是當(dāng)前線程所綁定的。

Thread類有一個(gè)類型為ThreadLocal.ThreadLocalMap的實(shí)例變量threadLocals,也就是說(shuō)每個(gè)線程有一個(gè)自己的ThreadLocalMap。

ThreadLocalMap有自己的獨(dú)立實(shí)現(xiàn),可以簡(jiǎn)單地將它的key視作ThreadLocal,value為代碼中放入的值(實(shí)際上key并不是ThreadLocal本身,而是它的一個(gè)弱引用)。

每個(gè)線程在往ThreadLocal里放值的時(shí)候,都會(huì)往自己的ThreadLocalMap里存,讀也是以ThreadLocal作為引用,在自己的map里找對(duì)應(yīng)的key,從而實(shí)現(xiàn)了線程隔離。

ThreadLocalMap有點(diǎn)類似HashMap的結(jié)構(gòu),只是HashMap是由數(shù)組+鏈表實(shí)現(xiàn)的,而ThreadLocalMap中并沒(méi)有鏈表結(jié)構(gòu)。

我們還要注意Entry, 它的key是ThreadLocal<?> k ,繼承自WeakReference, 也就是我們常說(shuō)的弱引用類型。.

為了搞清楚這個(gè)問(wèn)題,我們需要搞清楚Java的四種引用類型:

強(qiáng)引用:我們常常new出來(lái)的對(duì)象就是強(qiáng)引用類型,只要強(qiáng)引用存在,垃圾回收器將永遠(yuǎn)不會(huì)回收被引用的對(duì)象,哪怕內(nèi)存不足的時(shí)候

軟引用:使用SoftReference修飾的對(duì)象被稱為軟引用,軟引用指向的對(duì)象在內(nèi)存要溢出的時(shí)候被回收

弱引用:使用WeakReference修飾的對(duì)象被稱為弱引用,只要發(fā)生垃圾回收,若這個(gè)對(duì)象只被弱引用指向,那么就會(huì)被回收

虛引用:虛引用是最弱的引用,在 Java 中使用 PhantomReference 進(jìn)行定義。虛引用中唯一的作用就是用隊(duì)列接收對(duì)象即將死亡的通知.

這個(gè)問(wèn)題剛開(kāi)始看,如果沒(méi)有過(guò)多思考,弱引用,還有垃圾回收,那么肯定會(huì)覺(jué)得是null。

其實(shí)是不對(duì)的,因?yàn)轭}目說(shuō)的是在做 threadlocal.get() 操作,證明其實(shí)還是有強(qiáng)引用存在的,所以 key 并不為 null,如下圖所示,ThreadLocal的強(qiáng)引用仍然是存在的

如果我們的強(qiáng)引用不存在的話,那么 key 就會(huì)被回收,也就是會(huì)出現(xiàn)我們 value 沒(méi)被回收,key 被回收,導(dǎo)致 value 永遠(yuǎn)存在,出現(xiàn)內(nèi)存泄漏。

HashMap中解決沖突的方法是在數(shù)組上構(gòu)造一個(gè)鏈表結(jié)構(gòu),沖突的數(shù)據(jù)掛載到鏈表上,如果鏈表長(zhǎng)度超過(guò)一定數(shù)量則會(huì)轉(zhuǎn)化成紅黑樹(shù)。

而ThreadLocalMap中并沒(méi)有鏈表結(jié)構(gòu),所以這里不能適用HashMap解決沖突的方式了。

如果我們插入一個(gè)value=27的數(shù)據(jù),通過(guò)hash計(jì)算后應(yīng)該落入第4個(gè)槽位中,而槽位4已經(jīng)有了Entry數(shù)據(jù)。

此時(shí)就會(huì)線性向后查找,一直找到Entry為null的槽位才會(huì)停止查找,將當(dāng)前元素放入此槽位中。當(dāng)然迭代過(guò)程中還有其他的情況,比如遇到了Entry不為null且key值相等的情況,還有Entry中的key值為null的情況等等都會(huì)有不同的處理,后面會(huì)一一詳細(xì)講解。

這里還畫了一個(gè)Entry中的key為null的數(shù)據(jù)(Entry=2的灰色塊數(shù)據(jù)),因?yàn)閗ey值是弱引用類型,所以會(huì)有這種數(shù)據(jù)存在。在set過(guò)程中,如果遇到了key過(guò)期的Entry數(shù)據(jù),實(shí)際上是會(huì)進(jìn)行一輪探測(cè)式清理操作的,具體操作方式后面會(huì)講到

 實(shí)際的通過(guò)ThreadLocal創(chuàng)建的副本是存儲(chǔ)在每個(gè)線程自己的threadLocals中的;

為何threadLocals的類型ThreadLocalMap的鍵值為ThreadLocal對(duì)象,因?yàn)槊總€(gè)線程中可有多個(gè)threadLocal變量,就像上面代碼中的longLocal和stringLocal;

ThreadLocal和Synchonized都用于解決多線程并發(fā)訪問(wèn)。但是ThreadLocal與synchronized有本質(zhì)的區(qū)別。synchronized是利用鎖的機(jī)制,

使變量或代碼塊在某一時(shí)該只能被一個(gè)線程訪問(wèn)。而ThreadLocal為每一個(gè)線程都提供了變量的副本,

使得每個(gè)線程在某一時(shí)間訪問(wèn)到的并不是同一個(gè)對(duì)象,這樣就隔離了多個(gè)線程對(duì)數(shù)據(jù)的數(shù)據(jù)共享。而Synchronized卻正好相反,它用于在多個(gè)線程間通信時(shí)能夠獲得數(shù)據(jù)共享

如何在Java中實(shí)現(xiàn)線程?

在語(yǔ)言層面有三種方式。java.lang.Thread 類的實(shí)例就是一個(gè)線程但是它需要調(diào)用java.lang.Runnable接口來(lái)執(zhí)行,

由于線程類本身就是調(diào)用的Runnable接口所以你可以繼承 java.lang.Thread 類或者直接調(diào)用Runnable接口來(lái)重寫run()方法實(shí)現(xiàn)線程。

第三種 實(shí)現(xiàn)Callable<>接口并重寫call方法

Java中Runnable和Callable有什么不同?

Runnable和Callable都代表那些要在不同的線程中執(zhí)行的任務(wù)。

Runnable從JDK1.0開(kāi)始就有了,Callable是在 JDK1.5增加的。

它們的主要區(qū)別是Callable的 call() 方法可以返回值和拋出異常,而Runnable的run()方法沒(méi)有這些功能。

Callable可以返回裝載有計(jì)算結(jié)果的Future對(duì)象

什么是線程安全?Vector是一個(gè)線程安全類嗎?

如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,

而且其他的變量 的值也和預(yù)期的是一樣的,就是線程安全的。一個(gè)線程安全的計(jì)數(shù)器類的同一個(gè)實(shí)例對(duì)象在被多個(gè)線程使用的情況下也不會(huì)出現(xiàn)計(jì)算失誤。

很顯然你可以將集合類分 成兩組,線程安全和非線程安全的。Vector 是用同步方法來(lái)實(shí)現(xiàn)線程安全的, 而和它相似的ArrayList不是線程安全的。

Java中notify 和 notifyAll有什么區(qū)別?

這又是一個(gè)刁鉆的問(wèn)題,因?yàn)槎嗑€程可以等待單監(jiān)控鎖,Java API 的設(shè)計(jì)人員提供了一些方法當(dāng)?shù)却龡l件改變的時(shí)候通知它們,但是這些方法沒(méi)有完全實(shí)現(xiàn)。

notify()方法不能喚醒某個(gè)具體的線程,所以只有一個(gè)線程在等 待的時(shí)候它才有用武之地。

而notifyAll()喚醒所有線程并允許他們爭(zhēng)奪鎖確保了至少有一個(gè)線程能繼續(xù)運(yùn)行。

為什么wait, notify 和 notifyAll這些方法不在thread類里面?

這是個(gè)設(shè)計(jì)相關(guān)的問(wèn)題,它考察的是面試者對(duì)現(xiàn)有系統(tǒng)和一些普遍存在但看起來(lái)不合理的事物的看法?;卮疬@些問(wèn)題的時(shí)候,

你要說(shuō)明為什么把這些方法放在 Object類里是有意義的,還有不把它放在Thread類里的原因。一個(gè)很明顯的原因是JAVA提供的鎖是對(duì)象級(jí)的而不是線程級(jí)的,

每個(gè)對(duì)象都有鎖,通 過(guò)線程獲得。如果線程需要等待某些鎖那么調(diào)用對(duì)象中的wait()方法就有意義了。

如果wait()方法定義在Thread類中,線程正在等待的是哪個(gè)鎖 就不明顯了。簡(jiǎn)單的說(shuō),

由于wait,notify和notifyAll都是鎖級(jí)別的操作,所以把他們定義在Object類中因?yàn)殒i屬于對(duì)象。

什么是FutureTask?

在Java并發(fā)程序中FutureTask表示一個(gè)可以取消的異步運(yùn)算。它有啟動(dòng)和取消運(yùn)算、查詢運(yùn)算是否完成和取回運(yùn)算結(jié)果等方法。

只有當(dāng)運(yùn)算完 成的時(shí)候結(jié)果才能取回,如果運(yùn)算尚未完成get方法將會(huì)阻塞。一個(gè)FutureTask對(duì)象可以對(duì)調(diào)用了Callable和Runnable的對(duì)象進(jìn)行包 裝,

由于FutureTask也是調(diào)用了Runnable接口所以它可以提交給Executor來(lái)執(zhí)行。

有哪些不同的線程生命周期?

        當(dāng)我們?cè)贘ava程序中新建一個(gè)線程時(shí),它的狀態(tài)是New。當(dāng)我們調(diào)用線程的start()方法時(shí),

狀態(tài)被改變?yōu)镽unnable。線程調(diào)度器會(huì)為Runnable線程池中的線程分配CPU時(shí)間并且講它們的狀態(tài)改變?yōu)镽unning。

什么是死鎖(Deadlock)?如何分析和避免死鎖?

        死鎖是指兩個(gè)以上的線程永遠(yuǎn)阻塞的情況,這種情況產(chǎn)生至少需要兩個(gè)以上的線程和兩個(gè)以上的資源。

        分析死鎖,我們需要查看Java應(yīng)用程序的線程轉(zhuǎn)儲(chǔ)。我們需要找出那些狀態(tài)為BLOCKED的線程和他們等待的資源。每個(gè)資源都有一個(gè)唯一的id,用這個(gè)id我們可以找出哪些線程已經(jīng)擁有了它的對(duì)象鎖。

        避免嵌套鎖,只在需要的地方使用鎖和避免無(wú)限期等待是避免死鎖的通常辦法其他的線程狀態(tài)還有Waiting,Blocked 和Dead。

到此,相信大家對(duì)“Java線程面試題有哪些”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI