JAVA寶典-面試題-多線程篇(含答案)
下面是Java線程相關(guān)的高頻面試題(含答案),你可以用它來好好準(zhǔn)備面試。
1.并行和并發(fā)有什么區(qū)別?
2.進(jìn)程和線程的區(qū)別與聯(lián)系?
3.守護(hù)線程是什么?
4.創(chuàng)建線程有哪幾種方式?
5.說一下 runnable 和 callable 有什么區(qū)別?
6.線程有哪些狀態(tài)?
7.sleep() 和 wait() 有什么區(qū)別?
8.notify()和 notifyAll()有什么區(qū)別?
9.線程的 run() 和 start() 有什么區(qū)別?
10.創(chuàng)建線程池有哪幾種方式?
11.Java線程池中submit() 和 execute()方法的區(qū)別
12.在 java 程序中怎么保證多線程的運(yùn)行安全?
13.多線程鎖的升級原理是什么?
14. 什么是死鎖?什么是活鎖? 什么是線程饑餓?
15.ThreadLocal 是什么?有哪些使用場景?
16.說一下synchronized底層實(shí)現(xiàn)原理?
17.synchronized和volatile的區(qū)別是什么?
18.synchronized和Lock的區(qū)別是什么?
19.說一下 你對atomic 的理解 ?
20.說一下 你對Semaphore 的理解 ?
給個(gè)關(guān)注,再給一個(gè)小紅心
1.并行和并發(fā)有什么區(qū)別?
并發(fā):是指多個(gè)線程任務(wù)在同一個(gè)CPU上快速地輪換執(zhí)行,由于切換的速度非???,給人的感覺就是這些線程任務(wù)是在同時(shí)進(jìn)行的,但其實(shí)并發(fā)只是一種邏輯上的同時(shí)進(jìn)行; 并行:是指多個(gè)線程任務(wù)在不同CPU上同時(shí)進(jìn)行,是真正意義上的同時(shí)執(zhí)行。
2.進(jìn)程和線程的區(qū)別與聯(lián)系?
區(qū)別
并發(fā)性:不僅進(jìn)程之間可以并發(fā)執(zhí)行,同一個(gè)進(jìn)程的多個(gè)線程之間也可并發(fā)執(zhí)行。
擁有資源:進(jìn)程是擁有資源的一個(gè)獨(dú)立單位,線程不擁有系統(tǒng)資源,但可以訪問隸屬于進(jìn)程的資源。
系統(tǒng)開銷:多進(jìn)程的程序要比多線程的程序健壯,但在進(jìn)程切換時(shí),耗費(fèi)資源較大,效率要差一些。
線程和進(jìn)程在使用上各有優(yōu)缺點(diǎn):線程執(zhí)行開銷小,但不利于資源的管理和保護(hù);而進(jìn)程正相反。同時(shí),線程適合于在SMP機(jī)器上運(yùn)行,而進(jìn)程則可以跨機(jī)器遷移。
聯(lián)系
一個(gè)線程只能屬于一個(gè)進(jìn)程,而一個(gè)進(jìn)程可以有多個(gè)線程,但至少有一個(gè)線程;
資源分配給進(jìn)程,同一進(jìn)程的所有線程共享該進(jìn)程的所有資源;
處理機(jī)分給線程,即真正在處理機(jī)上運(yùn)行的是線程;
線程在執(zhí)行過程中,需要協(xié)作同步。不同進(jìn)程的線程間要利用消息通信的辦法實(shí)現(xiàn)同步。
3.守護(hù)線程是什么?
護(hù)線程是程序運(yùn)行的時(shí)候在后臺提供一種通用服務(wù)的線程。所有用戶線程停止,進(jìn)程會停掉所有守護(hù)線程,退出程序。
守護(hù)線程擁有自動結(jié)束自己生命周期的特性,而非守護(hù)線程不具備這個(gè)特點(diǎn)。
應(yīng)用場景:
JVM 中的垃圾回收線程就是典型的守護(hù)線程, 當(dāng) JVM 要退出時(shí),垃圾回收線程也會結(jié)束自己的生命周期.
4.創(chuàng)建線程有哪幾種方式?
1.繼承Thread類
2.實(shí)現(xiàn)Runnable接口
3.實(shí)現(xiàn)Callable接口
4.通過線程池創(chuàng)建線程(ThreadPollExecutor,ExecutorService..)
5.說一下 runnable 和 callable 有什么區(qū)別?
Runnable和Callable 都是接口,分別提供run方法和call方法
Runnable的run方法無返回值,Callable的call方法提供返回值來表示任務(wù)運(yùn)行結(jié)果
Runnable無法通過throws拋出異常,所有CheckedException必須在run方法內(nèi)部處理。Callable可直接拋出Exception異常.
Runnable可以作為Thread構(gòu)造器的參數(shù),通過開啟新的線程來執(zhí)行,也可以通過線程池來執(zhí)行。而Callable通過提交給線程池執(zhí)行
6.線程有哪些狀態(tài)?
這里要注意審題,是系統(tǒng)線程狀態(tài)還是Java中線程的狀態(tài)?
Java中線程的狀態(tài)
java.lang.Thread類
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
操作系統(tǒng)中線程的狀態(tài)
初始狀態(tài)(NEW)
對應(yīng) Java中的NEW
可運(yùn)行狀態(tài)(READY)
對應(yīng) Java中的 RUNNBALE 狀態(tài)
運(yùn)行狀態(tài)(RUNNING)
對應(yīng) Java中的 RUNNBALE 狀態(tài)
等待狀態(tài)(WAITING)
該狀態(tài)在 Java中被劃分為了 BLOCKED,WAITING,TIMED_WAITING 三種狀態(tài)
當(dāng)線程調(diào)用阻塞式 API時(shí),進(jìn)程(線程)進(jìn)入等待狀態(tài),這里指的是操作系統(tǒng)層面的。從 JVM層面來說,Java線程仍然處于 RUNNABLE 狀態(tài)。
JVM 并不關(guān)心操作系統(tǒng)線程的實(shí)際狀態(tài),從 JVM 看來,等待CPU使用權(quán)(操作系統(tǒng)狀態(tài)為可運(yùn)行態(tài))與等待 I/O(操作系統(tǒng)處于等待狀態(tài))沒有區(qū)別,都是在等待某種資源,所以都?xì)w入RUNNABLE 狀態(tài)
終止?fàn)顟B(tài) (DEAD)
對應(yīng) TERMINATED
7.sleep() 和 wait() 有什么區(qū)別?
sleep()和wait()都是線程暫停執(zhí)行的方法。
sleep方法屬于Thread類中的靜態(tài)方法,wait屬于Object的成員方法。
sleep()不涉及線程通信,調(diào)用時(shí)會暫停此線程指定的時(shí)間,但監(jiān)控依然保持,不會釋放對象鎖,到時(shí)間自動恢復(fù)
wait() 用于線程間的通信,調(diào)用時(shí)會放棄對象鎖**,進(jìn)入**等待隊(duì)列,待調(diào)用notify()/notifyAll()喚醒指定的線程或者所有線程,才進(jìn)入對象鎖定池準(zhǔn)備重新獲得對象鎖進(jìn)入運(yùn)行狀態(tài)。
wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用(使用范圍)
sleep()方法必須捕獲異常InterruptedException,而wait()\notify()以及notifyAll()不需要捕獲異常。
8.notify()和 notifyAll()有什么區(qū)別?
notify() 方法隨機(jī)喚醒對象的
等待池中的一個(gè)線程,進(jìn)入鎖池;notifyAll() 喚醒對象的等待池中的所有線程,進(jìn)入
鎖池。
等待池:假設(shè)線程A調(diào)用了某個(gè)對象的wait()方法,線程A就會釋放該對象的鎖,并進(jìn)入該對象的等待池,等待池中的線程不會去競爭該對象的鎖。 鎖池:只有獲取了對象的鎖,線程才能執(zhí)行對象的 synchronized 代碼,對象的鎖每次只有一個(gè)線程可以獲得,其他線程只能在鎖池中等待
9.線程的 run() 和 start() 有什么區(qū)別?
調(diào)用 start() 方法是用來啟動線程的,輪到該線程執(zhí)行時(shí),會自動調(diào)用 run();
調(diào)用 run() 方法,無法達(dá)到啟動多線程的目的,相當(dāng)于主線程線性執(zhí)行 Thread 對象的 run() 方法。
一個(gè)線程對線的 start() 方法只能調(diào)用一次,多次調(diào)用會拋出 java.lang.IllegalThreadStateException 異常;而run() 方法沒有限制。
10.創(chuàng)建線程池有哪幾種方式?
通過Executors工廠方法創(chuàng)建 (阿里巴巴開發(fā)規(guī)約中不建議使用此種方式創(chuàng)建線程池) 通過new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) 自定義創(chuàng)建(推薦)
11.Java線程池中submit() 和 execute()方法的區(qū)別
兩個(gè)方法都可以向線程池提交任務(wù)
execute()方法的返回類型是void,它定義在Executor接口中。
submit()方法可以返回持有計(jì)算結(jié)果的Future對象,它定義在ExecutorService接口中,它擴(kuò)展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。
12.在 java 程序中怎么保證多線程的運(yùn)行安全?
程序中保證多線程運(yùn)行安全的方式:
1.使用安全類,比如 Java. util. concurrent 下的類。
2.使用自動鎖 synchronized。
3.使用手動鎖 Lock 例如Reentrantlock。
4.保證一個(gè)或者多個(gè)操作在CPU執(zhí)行的過程中不被中斷。(原子性)
5.保證一個(gè)線程對共享變量的修改,另外一個(gè)線程能夠立刻看到。(可見性)
6.保證程序執(zhí)行的順序按照代碼的先后順序執(zhí)行。(有序性)
注意回答中不能缺少這3種特性
線程的安全性問題體現(xiàn)在:
原子性:一個(gè)或者多個(gè)操作在 CPU 執(zhí)行的過程中不被中斷的特性 可見性:一個(gè)線程對共享變量的修改,另外一個(gè)線程能夠立刻看到 有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行
13.多線程鎖的升級原理是什么?
鎖的級別從低到高:
無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖
鎖分級別原因:
沒有優(yōu)化以前,synchronized是重量級鎖(悲觀鎖),使用 wait 和 notify、notifyAll 來切換線程狀態(tài)非常消耗系統(tǒng)資源;線程的掛起和喚醒間隔很短暫,這樣很浪費(fèi)資源,影響性能。所以 JVM 對 synchronized 關(guān)鍵字進(jìn)行了優(yōu)化,把鎖分為 無鎖、偏向鎖、輕量級鎖、重量級鎖 狀態(tài)。
在學(xué)習(xí)并發(fā)編程知識synchronized時(shí),我們總是難以理解其實(shí)現(xiàn)原理,因?yàn)槠蜴i、輕量級鎖、重量級鎖都涉及到對象頭,所以了解java對象頭是我們深入了解synchronized的前提條件.這篇文章包含了對象頭的解析以及鎖膨脹過程的解析:
14. 什么是死鎖?什么是活鎖? 什么是線程饑餓?
15.ThreadLocal 是什么?有哪些使用場景?
1.ThreadLocal 介紹
2.ThreadLocal 應(yīng)用
3.ThreadLocal 源碼解析
3.1解決 Hash 沖突
4.ThreadLocal 特性
5..ThreadLocal 內(nèi)存泄露問題
16.說一下synchronized底層實(shí)現(xiàn)原理?
前置知識,需要了解 對象頭.-->JAVA對象布局之對象頭(Object Header)
同步代碼塊是通過 monitorenter 和 monitorexit 指令獲取線程的執(zhí)行權(quán)
monitorenter,如果當(dāng)前monitor的進(jìn)入數(shù)為0時(shí),線程就會進(jìn)入monitor,并且把進(jìn)入數(shù)+1,那么該線程就是monitor的擁有者(owner)。
如果該線程已經(jīng)是monitor的擁有者,又重新進(jìn)入,就會把進(jìn)入數(shù)再次+1。也就是可重入的。
執(zhí)行monitorexit的線程必須是monitor的擁有者,指令執(zhí)行后,monitor的進(jìn)入數(shù)減1,如果減1后進(jìn)入數(shù)為0,則該線程會退出monitor。其他被阻塞的線程就可以嘗試去獲取monitor的所有權(quán)。
monitorexit指令出現(xiàn)了兩次,第1次為同步正常退出釋放鎖;第2次為發(fā)生異步退出釋放鎖;
同步方法通過加 ACC_SYNCHRONIZED 標(biāo)識實(shí)現(xiàn)線程的執(zhí)行權(quán)的控制
標(biāo)志位ACC_SYNCHRONIZED,作用就是一旦執(zhí)行到這個(gè)方法時(shí),就會先判斷是否有標(biāo)志位,如果有這個(gè)標(biāo)志位,就會先嘗試獲取monitor,獲取成功才能執(zhí)行方法,方法執(zhí)行完成后再釋放monitor。在方法執(zhí)行期間,其他線程都無法獲取同一個(gè)monitor。歸根結(jié)底還是對monitor對象的爭奪,只是同步方法是一種隱式的方式來實(shí)現(xiàn)。
總的來說,synchronized的底層原理是通過monitor對象來完成的
17.synchronized和volatile的區(qū)別是什么?
作用:
synchronized 表示只有一個(gè)線程可以獲取作用對象的鎖,執(zhí)行代碼,阻塞其他線程。 volatile 表示變量在 CPU 的寄存器中是不確定的,必須從主存中讀取。保證多線程環(huán)境下變量的可見性;禁止指令重排序。
區(qū)別:
synchronized 可以作用于變量、方法、對象;volatile 只能作用于變量。 synchronized 可以保證線程間的有序性、原子性和可見性;volatile 只保證了可見性和有序性,無法保證原子性。 synchronized 線程阻塞,volatile 線程不阻塞。
18.synchronized和Lock的區(qū)別是什么?
在多線程情況下,鎖是線程控制的重要途徑。Java為此也提供了2種鎖機(jī)制,synchronized和lock。
我們這里不討論具體的實(shí)現(xiàn)原理和細(xì)節(jié),只討論它們的區(qū)別
如果有小伙伴有興趣更深入了解它們,請關(guān)注公眾號:JAVA寶典
區(qū)別
lock是一個(gè)接口,而synchronized是java的一個(gè)關(guān)鍵字。
synchronized在發(fā)生異常時(shí)會自動釋放占有的鎖,因此不會出現(xiàn)死鎖;而lock發(fā)生異常時(shí),不會主動釋放占有的鎖,必須手動來釋放鎖,可能引起死鎖的發(fā)生(也稱隱式鎖和顯式鎖)
lock等待鎖過程中可以用interrupt來中斷等待,而synchronized只能等待鎖的釋放,不能響應(yīng)中斷;
Lock可以通過trylock來知道有沒有獲取鎖,而synchronized不能;
Lock可以提高多個(gè)線程進(jìn)行讀操作的效率。(可以通過readwritelock實(shí)現(xiàn)讀寫分離)
在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當(dāng)競爭資源非常激烈時(shí)(即有大量線程同時(shí)競爭),此時(shí)Lock的性能要優(yōu)于synchronized。在使用時(shí)要根據(jù)適當(dāng)情況選擇。
synchronized 是 JVM 層面實(shí)現(xiàn)的;Lock 是 JDK 代碼層面實(shí)現(xiàn)
synchronized使用Object對象本身的wait 、notify、notifyAll調(diào)度機(jī)制,而Lock可以使用Condition進(jìn)行線程之間的調(diào)度
繼上一條,synchronized只有一個(gè)阻塞隊(duì)列,而Lock使用Condition可以有多個(gè)阻塞隊(duì)列
synchronized和lock的用法區(qū)別
synchronized:在需要同步的對象中加入此控制,synchronized可以加在方法上,也可以加在特定代碼塊中,括號中表示需要鎖的對象。
lock:一般使用ReentrantLock類做為鎖。在加鎖和解鎖處需要通過lock()和unlock()顯示指出。所以一般會在finally塊中寫unlock()以防死鎖。
19.說一下 你對atomic 的理解 ?
在JDK5.0之前,想要實(shí)現(xiàn)無鎖無等待的算法是不可能的,除非用本地庫,自從有了Atomic變量類后,這成為可能。
在java.util.concurrent.atomic包下有這些類:
標(biāo)量類:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference 數(shù)組類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray 更新器類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater 復(fù)合變量類:AtomicMarkableReference,AtomicStampedReference
拿AtomicInteger來舉例,其內(nèi)部實(shí)現(xiàn)不是簡單的使用synchronized,而是一個(gè)更為高效的方式CAS (compare and swap) + volatile和native方法,從而避免了synchronized的高開銷,執(zhí)行效率大為提升。
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
這里直接調(diào)用一個(gè)叫Unsafe的類去處理,這個(gè)類是用于執(zhí)行低級別、不安全操作的方法集合。盡管這個(gè)類和所有的方法都是公開的(public),但是這個(gè)類的使用仍然受限,你無法在自己的java程序中直接使用該類,因?yàn)橹挥惺谛诺拇a才能獲得該類的實(shí)例。所以我們平時(shí)的代碼是無法使用這個(gè)類的,因?yàn)槠湓O(shè)計(jì)的操作過于偏底層,如若操作不慎可能會帶來很大的災(zāi)難,所以直接禁止普通代碼的訪問,當(dāng)然JDK使用是沒有問題的。
關(guān)于CAS 在我的另一篇文章: 什么是CAS,ABA問題怎么解決?
20.說一下 你對Semaphore 的理解 ?
Semaphore就是一個(gè)信號量,它的作用是限制某段代碼塊的并發(fā)數(shù)。 Semaphore有一個(gè)構(gòu)造函數(shù),可以傳入一個(gè)int型整數(shù)n,表示某段代碼最多只有n個(gè)線程可以訪問, 如果超出了n,那么請等待,等到某個(gè)線程執(zhí)行完畢這段代碼塊,下一個(gè)線程再進(jìn)入。 由此可以看出如果Semaphore構(gòu)造函數(shù)中傳入的int型整數(shù)n=1,相當(dāng)于變成了一個(gè)synchronized了。
Semaphore類位于java.util.concurrent包下,它提供了2個(gè)構(gòu)造器:
//參數(shù)permits表示許可數(shù)目,即同時(shí)可以允許多少線程進(jìn)行訪問
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
//這個(gè)多了一個(gè)參數(shù)fair表示是否是公平的,即等待時(shí)間越久的越先獲取許可
public Semaphore(int permits, boolean fair) {
sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}
Semaphore類中比較重要的幾個(gè)方法,首先是acquire()、release()方法: acquire()用來獲取一個(gè)許可,若無許可能夠獲得,則會一直等待,直到獲得許可。 release()用來釋放許可。注意,在釋放許可之前,必須先獲獲得許可。
//嘗試獲取一個(gè)許可,若獲取成功,則立即返回true,若獲取失敗,則立即返回false
public boolean tryAcquire() { };
//嘗試獲取一個(gè)許可,若在指定的時(shí)間內(nèi)獲取成功,則立即返回true,否則則立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };
//嘗試獲取permits個(gè)許可,若獲取成功,則立即返回true,若獲取失敗,則立即返回false
public boolean tryAcquire(int permits) { };
//嘗試獲取permits個(gè)許可,若在指定的時(shí)間內(nèi)獲取成功,則立即返回true
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { };
//得到當(dāng)前可用的許可數(shù)目
public int availablePermits();
掃描二維碼
獲取更多精彩
JAVA寶典

喜歡我們就多一個(gè)點(diǎn)贊,多一次分享吧!
