2019 Java面試問題及答案詳解(三)Java多線程篇

1. 並行和併發有什麼區別?

並行:是兩個任務同時運行,就是甲任務進行的同時,乙任務也在進行。(需要多核CPU) 併發:指兩個任務都請求運行,而處理器只能按受一個任務,就把這兩個任務安排輪流進行,由於時間間隔較短,使人感覺兩個任務都在運行。(表面看是CPU在同時執行多個任務,其實實際上是因為CPU瞬間切換到其他任務的速度特別快,在不同的任務之間一直在不停的切換,給不同的任務分配了不同的時間。)

2. 線程和進程的區別?

(1)進程是一段正在執行的程序,是資源分配的基本單元,而線程是CPU調度的基本單元。 (2)進程間相互獨立進程,進程之間不能共享資源,一個進程至少有一個線程,同一進程的各線程共享整個進程的資源(寄存器、堆棧、上下文)。 3、線程的創建和切換開銷比進程小

3. 守護線程是什麼?

java提供了倆類的線程:用戶線程和守護線程(user thread and Daemon thread)。

用戶線程是高優先級的線程。JVM虛擬機在結束一個用戶線程之前,會先等待該用戶線程完成它的task。

在另一方面,守護線程是低優先級的線程,它的作用僅僅是為用戶線程提供服務。

正是由於守護線程是為用戶線程提供服務的,僅僅在用戶線程處於運行狀態時才需要守護線程。

另外,一旦所有的用戶線程都運行完畢,那麼守護線程是無法阻止JVM退出的。

這也是存在於守護線程中的無限循環不會產生問題的原因,因為包括finally 塊的任何代碼都不會被執行,一旦所有的用戶線程結束運行之後。

出於這個原因,我們不推薦使用守護線程處理I/O任務。(原為來源:https://www.jianshu.com/p/6a4e303d9f42)

4. 創建線程有哪幾種方式?

(1)繼承Thread類

(2)實現Runnable接口

一個類如果實現了Runnable接口或者繼承了Thread類,那麼它就是一個多線程類,如果是要實現多線程,還需要重寫run()方法,所以run() 方法是多線程的入口

5. 說一下 runnable 和 callable 有什麼區別?

兩者最大的不同點是:實現Callable接口的任務線程能返回執行結果;而實現Runnable接口的任務線程不能返回結果;

Callable接口的call()方法允許拋出異常;而Runnable接口的run()方法的異常只能在內部消化,不能繼續上拋;

Callable接口支持返回執行結果,此時需要調用FutureTask.get()方法實現,此方法會阻塞主線程直到獲取結果;當不調用此方法時,主線程不會阻塞!

6. 線程有哪些狀態?

線程狀態有 5 種,新建,就緒,運行,阻塞,死亡。 如圖所示:

2019 Java面試問題及答案詳解(三)Java多線程篇

2019 Java面試問題及答案詳解(三)Java多線程篇

7. sleep() 和 wait() 有什麼區別?

這兩個方法來自不同的類分別是Thread和Object 最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法(鎖代碼塊和方法鎖)。

wait,notify和notifyAll只能在同步控制方法或者同步控制塊裡面使用,而sleep可以在任何地方使用(使用範圍) sleep必須捕獲異常,而wait,notify和notifyAll不需要捕獲異常

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

notify只會隨機選取一個處於等待池中的線程進入鎖池去競爭獲取鎖的機會

notifyAll會讓所有處於等待池的線程全部進入鎖池去競爭獲取鎖的機會

9. 線程的 run()和 start()有什麼區別?

run()方法:是在主線程中執行方法,和調用普通方法一樣;(按順序執行,同步執行)

start()方法:是創建了新的線程,在新的線程中執行;(異步執行)

10.創建線程池有哪幾種方式?

Java通過Executors提供四種線程池,分別為:

* newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。

* newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。

* newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。

* newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

11.線程池都有哪些狀態?

線程池的5種狀態:Running、ShutDown、Stop、Tidying、Terminated

2019 Java面試問題及答案詳解(三)Java多線程篇

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

接收的參數不一樣

返回值不一樣

submit方便Exception處理

13. 在 java 程序中怎麼保證多線程的運行安全?

線程的安全性問題體現在:

原子性:一個或者多個操作在 CPU 執行的過程中不被中斷的特性

可見性:一個線程對共享變量的修改,另外一個線程能夠立刻看到

有序性:程序執行的順序按照代碼的先後順序執行 導致原因:

緩存導致的可見性問題

線程切換帶來的原子性問題

編譯優化帶來的有序性問題 解決辦法:

JDK Atomic開頭的原子類、synchronized、LOCK,可以解決原子性問題

synchronized、volatile、LOCK,可以解決可見性問題

Happens-Before 規則可以解決有序性問題 Happens-Before 規則如下:

程序次序規則:在一個線程內,按照程序控制流順序,書寫在前面的操作先行發生於書寫在後面的操作

管程鎖定規則:一個unlock操作先行發生於後面對同一個鎖的lock操作

volatile變量規則:對一個volatile變量的寫操作先行發生於後面對這個變量的讀操作

線程啟動規則:Thread對象的start()方法先行發生於此線程的每一個動作

線程終止規則:線程中的所有操作都先行發生於對此線程的終止檢測

線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生

對象終結規則:一個對象的初始化完成(構造函數執行結束)先行發生於它的finalize()方法的開始

14. 多線程鎖的升級原理是什麼?

先說說為什麼會有鎖升級 因為Sycronized是重量級鎖(也是悲觀鎖),每次在要進行鎖的請求的時候,如果當前資源被其他線程佔有要將當前的線程阻塞加入到阻塞隊列,然後清空當前線程的緩存,等到鎖釋放的時候再通過notify或者notifyAll喚醒當前的線程,並讓其處於就緒狀態。這樣線程的來回切換是非常消耗系統資源的,而且有的時候,線程剛掛起資源就釋放了。而Java的線程是映射到操作系統的原生線程之上的每次線程的阻塞或者喚醒都要經過用戶態到核心態或者核心態到用戶態的轉化,這樣是十分浪費資源的,這樣就會造成性能上的降低,因此JVM對Sychronized進行了優化,將Sycronized分為三種鎖的級別:偏向鎖,輕量級鎖,重量級鎖。 很多的時候,對於一個可能發生併發訪問的對象而言,其實很少會被競爭,就算有些資源存在競爭也是在很少的一段時間資源就會被釋放,而這樣的情況下將線程掛起是十分浪費性能的。 偏向鎖(樂觀鎖): 當鎖對象第一次被線程獲取的時候,虛擬機會將鎖對象的對象頭中的鎖標誌位設置成為01,並將偏向鎖標誌設置為1,線程通過CAS的方式將自己的ID值放置到對象頭中(因為在這個過程中有可能會有其他線程來競爭鎖,所以要通過CAS的方式,一旦有競爭就會升級為輕量級鎖了),如果成功線程就獲得了該輕量級鎖。這樣每次再進入該鎖對象的時候不用進行任何的同步操作,直接比較當前鎖對象的對象頭是不是該線程的ID,如果是就可以直接進入。偏向鎖升級為輕量級鎖 偏向鎖是一種無競爭鎖,一旦出現了競爭大多數情況下就會升級為輕量級鎖。現在我們假設有線程1持有偏向鎖,線程2來競爭偏向鎖會經歷以下幾個過程: 1. 首先線程2會先檢查偏向鎖標記,如果是1,說明當前是偏向鎖,那麼JVM會找到線程1,看線程1是否還存活著2 2. 如果線程1已經執行完畢,就是說線程1不存在了(線程1自己是不會去釋放偏向鎖的),那麼先將偏向鎖置為0,對象頭設置成為無鎖的狀態,用CAS的方式嘗試將線程2的ID放入對象頭中,不進行鎖升級,還是偏向鎖 3. 如果線程1還活著,先暫停線程1,將鎖標誌位變成00(輕量級鎖)然後在線程1的棧幀中開闢出一塊空間(Display Mark Word)將對象頭的Mark Word置換到線程一的棧幀當中,而對象頭中此時存儲的是指向當前線程棧幀的指針。此時就變成了輕量級鎖。繼續執行線程1,然後線程2採用CAS的方式嘗試獲取鎖。輕量級鎖與偏向鎖最大的不同之處 輕量級鎖和偏向鎖的不同之處就在於輕量級鎖對於獲取鎖對象採用CAS的同步方式而偏向鎖直接是把整個同步過程給取消。輕量級鎖(樂觀鎖) 輕量級鎖如何創建在上面已經講過了,接下來說說輕量級鎖如何獲取鎖對象,輕量級鎖是通過CAS也就是自旋的方式嘗試獲取鎖對象,一旦失敗會先檢查,對象頭中存儲的是否是指向當前線程棧幀的指針,如果是,就可以獲取對象,如果不是說明存在競爭那麼就要膨脹為重量級鎖。輕量級鎖的解鎖也是通過CAS的方式嘗試將對象頭的Mark Word和線程中的Display Mark Word替換回來,如果成功,就釋放鎖,如果失敗說明還有許多其他等待鎖的線程(說明此時已經不是輕量級鎖而是重量級鎖了),會將這些線程喚醒,然後釋放鎖。輕量級鎖膨脹為重量級鎖 一旦有兩條以上的線程競爭鎖,輕量級鎖膨脹為重量級鎖,鎖的狀態變成10,此時對象頭中存儲的就是指向重量級鎖的棧幀的指針。而且其他等待鎖的線程要進入阻塞狀態,等待重量級鎖釋放再來被喚醒然後去競爭。

15. 什麼是死鎖?

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

16. 怎麼防止死鎖? 1、儘量使用tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),設置超時時間,超時可以退出防止死鎖。 2、儘量使用java.util.concurrent(jdk 1.5以上)包的併發類代替手寫控制併發,比較常用的是ConcurrentHashMap、ConcurrentLinkedQueue、AtomicBoolean等等,實際應用中java.util.concurrent.atomic十分有用,簡單方便且效率比使用Lock更高 。 3、儘量降低鎖的使用粒度,儘量不要幾個功能用同一把鎖 。 4、儘量減少同步的代碼塊。

17. synchronized 和 volatile 的區別是什麼?

共性:volatile與synchronized都用於保證多線程中數據的安全

區別:

(1)volatile修飾的變量,jvm每次都從主存(主內存)中讀取,而不會從寄存器(工作內存)中讀取。

而synchronized則是鎖住當前變量,同一時刻只有一個線程能夠訪問當前變量

(2)volatile僅能用在變量級別,而synchronized可用在變量和方法中

(3)volatie僅能實現變量的修改可見性,無法保證變量操作的原子性。而synchronized可以實現變量的修改可見性與原子性

(4)volatile不需要加鎖,因此不會造成線程的阻塞,而且比synchronized更輕量級,而synchronized可能導致線程的阻塞

18. synchronized 和 Lock 有什麼區別?

2019 Java面試問題及答案詳解(三)Java多線程篇

總結:關於多線程的常規面試問題大致如上。由於白天需要上班,所以這種文字描述的問題整理起來既浪費時間又有點枯燥無味。但是,又是我們必須理解必不可少的。整理完,一套問題後,以後後面大部分時間都用來詳細針對各個知識點做對應的demo。期待您的加入,一起學習一起鞏固,一起good good study,day day up!


分享到:


相關文章: