久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      Java并發(fā)基礎常見面試題(總結(jié))

      本篇文章給大家總結(jié)了一下Java并發(fā)基礎常見面試題,有一定的參考價值,有需要的朋友可以參考一下,希望對大家有所幫助。

      Java并發(fā)基礎常見面試題(總結(jié))

      1. 什么是線程和進程?

      1.1. 何為進程?

      進程是程序的一次執(zhí)行過程,是系統(tǒng)運行程序的基本單位,因此進程是動態(tài)的。系統(tǒng)運行一個程序即是一個進程從創(chuàng)建,運行到消亡的過程。

      在 Java 中,當我們啟動 main 函數(shù)時其實就是啟動了一個 JVM 的進程,而 main 函數(shù)所在的線程就是這個進程中的一個線程,也稱主線程。

      如下圖所示,在 windows 中通過查看任務管理器的方式,我們就可以清楚看到 window 當前運行的進程(.exe 文件的運行)。

      Java并發(fā)基礎常見面試題(總結(jié))

      1.2. 何為線程?

      線程與進程相似,但線程是一個比進程更小的執(zhí)行單位。一個進程在其執(zhí)行的過程中可以產(chǎn)生多個線程。與進程不同的是同類的多個線程共享進程的方法區(qū)資源,但每個線程有自己的程序計數(shù)器虛擬機棧本地方法棧,所以系統(tǒng)在產(chǎn)生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。

      Java 程序天生就是多線程程序,我們可以通過 JMX 來看一下一個普通的 Java 程序有哪些線程,代碼如下。

      public class MultiThread {     public static void main(String[] args) {         // 獲取 Java 線程管理 MXBean     ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();         // 不需要獲取同步的 monitor 和 synchronizer 信息,僅獲取線程和線程堆棧信息         ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);         // 遍歷線程信息,僅打印線程 ID 和線程名稱信息         for (ThreadInfo threadInfo : threadInfos) {             System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());         }     } }

      上述程序輸出如下(輸出內(nèi)容可能不同,不用太糾結(jié)下面每個線程的作用,只用知道 main 線程執(zhí)行 main 方法即可):

      [5] Attach Listener //添加事件 [4] Signal Dispatcher // 分發(fā)處理給 JVM 信號的線程 [3] Finalizer //調(diào)用對象 finalize 方法的線程 [2] Reference Handler //清除 reference 線程 [1] main //main 線程,程序入口

      從上面的輸出內(nèi)容可以看出:一個 Java 程序的運行是 main 線程和多個其他線程同時運行

      2. 請簡要描述線程與進程的關(guān)系,區(qū)別及優(yōu)缺點?

      從 JVM 角度說進程和線程之間的關(guān)系

      2.1. 圖解進程和線程的關(guān)系

      下圖是 Java 內(nèi)存區(qū)域,通過下圖我們從 JVM 的角度來說一下線程和進程之間的關(guān)系。如果你對 Java 內(nèi)存區(qū)域 (運行時數(shù)據(jù)區(qū)) 這部分知識不太了解的話可以閱讀一下這篇文章:《可能是把 Java 內(nèi)存區(qū)域講的最清楚的一篇文章》

      <p align="center">
      <img src="https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/JVM運行時數(shù)據(jù)區(qū)域.png" width="600px"/>
      </p>

      從上圖可以看出:一個進程中可以有多個線程,多個線程共享進程的方法區(qū) (JDK1.8 之后的元空間)資源,但是每個線程有自己的程序計數(shù)器虛擬機棧本地方法棧。

      總結(jié): 線程 是 進程 劃分成的更小的運行單位。線程和進程最大的不同在于基本上各進程是獨立的,而各線程則不一定,因為同一進程中的線程極有可能會相互影響。線程執(zhí)行開銷小,但不利于資源的管理和保護;而進程正相反

      下面是該知識點的擴展內(nèi)容!

      下面來思考這樣一個問題:為什么程序計數(shù)器、虛擬機棧本地方法棧是線程私有的呢?為什么堆和方法區(qū)是線程共享的呢?

      2.2. 程序計數(shù)器為什么是私有的?

      程序計數(shù)器主要有下面兩個作用:

      1. 字節(jié)碼解釋器通過改變程序計數(shù)器來依次讀取指令,從而實現(xiàn)代碼的流程控制,如:順序執(zhí)行、選擇、循環(huán)、異常處理。
      2. 在多線程的情況下,程序計數(shù)器用于記錄當前線程執(zhí)行的位置,從而當線程被切換回來的時候能夠知道該線程上次運行到哪兒了。

      需要注意的是,如果執(zhí)行的是 native 方法,那么程序計數(shù)器記錄的是 undefined 地址,只有執(zhí)行的是 Java 代碼時程序計數(shù)器記錄的才是下一條指令的地址。

      所以,程序計數(shù)器私有主要是為了線程切換后能恢復到正確的執(zhí)行位置。

      2.3. 虛擬機棧和本地方法棧為什么是私有的?

      • 虛擬機棧: 每個 Java 方法在執(zhí)行的同時會創(chuàng)建一個棧幀用于存儲局部變量表、操作數(shù)棧、常量池引用等信息。從方法調(diào)用直至執(zhí)行完成的過程,就對應著一個棧幀在 Java 虛擬機棧中入棧和出棧的過程。
      • 本地方法棧: 和虛擬機棧所發(fā)揮的作用非常相似,區(qū)別是: 虛擬機棧為虛擬機執(zhí)行 Java 方法 (也就是字節(jié)碼)服務,而本地方法棧則為虛擬機使用到的 Native 方法服務。 在 HotSpot 虛擬機中和 Java 虛擬機棧合二為一。

      所以,為了保證線程中的局部變量不被別的線程訪問到,虛擬機棧和本地方法棧是線程私有的。

      2.4. 一句話簡單了解堆和方法區(qū)

      堆和方法區(qū)是所有線程共享的資源,其中堆是進程中最大的一塊內(nèi)存,主要用于存放新創(chuàng)建的對象 (所有對象都在這里分配內(nèi)存),方法區(qū)主要用于存放已被加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。

      3. 說說并發(fā)與并行的區(qū)別?

      • 并發(fā): 同一時間段,多個任務都在執(zhí)行 (單位時間內(nèi)不一定同時執(zhí)行);
      • 并行: 單位時間內(nèi),多個任務同時執(zhí)行。

      4. 為什么要使用多線程呢?

      先從總體上來說:

      • 從計算機底層來說: 線程可以比作是輕量級的進程,是程序執(zhí)行的最小單位,線程間的切換和調(diào)度的成本遠遠小于進程。另外,多核 CPU 時代意味著多個線程可以同時運行,這減少了線程上下文切換的開銷。
      • 從當代互聯(lián)網(wǎng)發(fā)展趨勢來說: 現(xiàn)在的系統(tǒng)動不動就要求百萬級甚至千萬級的并發(fā)量,而多線程并發(fā)編程正是開發(fā)高并發(fā)系統(tǒng)的基礎,利用好多線程機制可以大大提高系統(tǒng)整體的并發(fā)能力以及性能。

      再深入到計算機底層來探討:

      • 單核時代: 在單核時代多線程主要是為了提高 CPU 和 IO 設備的綜合利用率。舉個例子:當只有一個線程的時候會導致 CPU 計算時,IO 設備空閑;進行 IO 操作時,CPU 空閑。我們可以簡單地說這兩者的利用率目前都是 50%左右。但是當有兩個線程的時候就不一樣了,當一個線程執(zhí)行 CPU 計算時,另外一個線程可以進行 IO 操作,這樣兩個的利用率就可以在理想情況下達到 100%了。
      • 多核時代: 多核時代多線程主要是為了提高 CPU 利用率。舉個例子:假如我們要計算一個復雜的任務,我們只用一個線程的話,CPU 只會一個 CPU 核心被利用到,而創(chuàng)建多個線程就可以讓多個 CPU 核心被利用到,這樣就提高了 CPU 的利用率。

      5. 使用多線程可能帶來什么問題?

      并發(fā)編程的目的就是為了能提高程序的執(zhí)行效率提高程序運行速度,但是并發(fā)編程并不總是能提高程序運行速度的,而且并發(fā)編程可能會遇到很多問題,比如:內(nèi)存泄漏、上下文切換、死鎖還有受限于硬件和軟件的資源閑置問題。

      6. 說說線程的生命周期和狀態(tài)?

      Java 線程在運行的生命周期中的指定時刻只可能處于下面 6 種不同狀態(tài)的其中一個狀態(tài)(圖源《Java 并發(fā)編程藝術(shù)》4.1.4 節(jié))。

      Java并發(fā)基礎常見面試題(總結(jié))

      線程在生命周期中并不是固定處于某一個狀態(tài)而是隨著代碼的執(zhí)行在不同狀態(tài)之間切換。Java 線程狀態(tài)變遷如下圖所示(圖源《Java 并發(fā)編程藝術(shù)》4.1.4 節(jié)):

      Java并發(fā)基礎常見面試題(總結(jié))

      由上圖可以看出:線程創(chuàng)建之后它將處于 NEW(新建) 狀態(tài),調(diào)用 start() 方法后開始運行,線程這時候處于 READY(可運行) 狀態(tài)??蛇\行狀態(tài)的線程獲得了 CPU 時間片(timeslice)后就處于 RUNNING(運行) 狀態(tài)。

      操作系統(tǒng)隱藏 Java 虛擬機(JVM)中的 RUNNABLE 和 RUNNING 狀態(tài),它只能看到 RUNNABLE 狀態(tài)(圖源:HowToDoInJava:Java Thread Life Cycle and Thread States),所以 Java 系統(tǒng)一般將這兩個狀態(tài)統(tǒng)稱為 RUNNABLE(運行中) 狀態(tài) 。

      Java并發(fā)基礎常見面試題(總結(jié))

      當線程執(zhí)行 wait()方法之后,線程進入 WAITING(等待) 狀態(tài)。進入等待狀態(tài)的線程需要依靠其他線程的通知才能夠返回到運行狀態(tài),而 TIME_WAITING(超時等待) 狀態(tài)相當于在等待狀態(tài)的基礎上增加了超時限制,比如通過 sleep(long millis)方法或 wait(long millis)方法可以將 Java 線程置于 TIMED WAITING 狀態(tài)。當超時時間到達后 Java 線程將會返回到 RUNNABLE 狀態(tài)。當線程調(diào)用同步方法時,在沒有獲取到鎖的情況下,線程將會進入到 BLOCKED(阻塞) 狀態(tài)。線程在執(zhí)行 Runnable 的 run() 方法之后將會進入到 TERMINATED(終止) 狀態(tài)。

      7. 什么是上下文切換?

      多線程編程中一般線程的個數(shù)都大于 CPU 核心的個數(shù),而一個 CPU 核心在任意時刻只能被一個線程使用,為了讓這些線程都能得到有效執(zhí)行,CPU 采取的策略是為每個線程分配時間片并輪轉(zhuǎn)的形式。當一個線程的時間片用完的時候就會重新處于就緒狀態(tài)讓給其他線程使用,這個過程就屬于一次上下文切換。

      概括來說就是:當前任務在執(zhí)行完 CPU 時間片切換到另一個任務之前會先保存自己的狀態(tài),以便下次再切換回這個任務時,可以再加載這個任務的狀態(tài)。任務從保存到再加載的過程就是一次上下文切換。

      上下文切換通常是計算密集型的。也就是說,它需要相當可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時間。所以,上下文切換對系統(tǒng)來說意味著消耗大量的 CPU 時間,事實上,可能是操作系統(tǒng)中時間消耗最大的操作。

      Linux 相比與其他操作系統(tǒng)(包括其他類 Unix 系統(tǒng))有很多的優(yōu)點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。

      8. 什么是線程死鎖?如何避免死鎖?

      8.1. 認識線程死鎖

      多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由于線程被無限期地阻塞,因此程序不可能正常終止。

      如下圖所示,線程 A 持有資源 2,線程 B 持有資源 1,他們同時都想申請對方的資源,所以這兩個線程就會互相等待而進入死鎖狀態(tài)。

      Java并發(fā)基礎常見面試題(總結(jié))

      下面通過一個例子來說明線程死鎖,代碼模擬了上圖的死鎖的情況 (代碼來源于《并發(fā)編程之美》):

      public class DeadLockDemo {     private static Object resource1 = new Object();//資源 1     private static Object resource2 = new Object();//資源 2      public static void main(String[] args) {         new Thread(() -> {             synchronized (resource1) {                 System.out.println(Thread.currentThread() + "get resource1");                 try {                     Thread.sleep(1000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 System.out.println(Thread.currentThread() + "waiting get resource2");                 synchronized (resource2) {                     System.out.println(Thread.currentThread() + "get resource2");                 }             }         }, "線程 1").start();          new Thread(() -> {             synchronized (resource2) {                 System.out.println(Thread.currentThread() + "get resource2");                 try {                     Thread.sleep(1000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 System.out.println(Thread.currentThread() + "waiting get resource1");                 synchronized (resource1) {                     System.out.println(Thread.currentThread() + "get resource1");                 }             }         }, "線程 2").start();     } }

      Output

      Thread[線程 1,5,main]get resource1 Thread[線程 2,5,main]get resource2 Thread[線程 1,5,main]waiting get resource2 Thread[線程 2,5,main]waiting get resource1

      線程 A 通過 synchronized (resource1) 獲得 resource1 的監(jiān)視器鎖,然后通過 Thread.sleep(1000);讓線程 A 休眠 1s 為的是讓線程 B 得到執(zhí)行然后獲取到 resource2 的監(jiān)視器鎖。線程 A 和線程 B 休眠結(jié)束了都開始企圖請求獲取對方的資源,然后這兩個線程就會陷入互相等待的狀態(tài),這也就產(chǎn)生了死鎖。上面的例子符合產(chǎn)生死鎖的四個必要條件。

      學過操作系統(tǒng)的朋友都知道產(chǎn)生死鎖必須具備以下四個條件:

      1. 互斥條件:該資源任意一個時刻只由一個線程占用。
      2. 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
      3. 不剝奪條件:線程已獲得的資源在末使用完之前不能被其他線程強行剝奪,只有自己使用完畢后才釋放資源。
      4. 循環(huán)等待條件:若干進程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。

      8.2. 如何避免線程死鎖?

      我們只要破壞產(chǎn)生死鎖的四個條件中的其中一個就可以了。

      破壞互斥條件

      這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)。

      破壞請求與保持條件

      一次性申請所有的資源。

      破壞不剝奪條件

      占用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源。

      破壞循環(huán)等待條件

      靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞循環(huán)等待條件。

      我們對線程 2 的代碼修改成下面這樣就不會產(chǎn)生死鎖了。

              new Thread(() -> {             synchronized (resource1) {                 System.out.println(Thread.currentThread() + "get resource1");                 try {                     Thread.sleep(1000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 System.out.println(Thread.currentThread() + "waiting get resource2");                 synchronized (resource2) {                     System.out.println(Thread.currentThread() + "get resource2");                 }             }         }, "線程 2").start();

      Output

      Thread[線程 1,5,main]get resource1 Thread[線程 1,5,main]waiting get resource2 Thread[線程 1,5,main]get resource2 Thread[線程 2,5,main]get resource1 Thread[線程 2,5,main]waiting get resource2 Thread[線程 2,5,main]get resource2  Process finished with exit code 0

      我們分析一下上面的代碼為什么避免了死鎖的發(fā)生?

      線程 1 首先獲得到 resource1 的監(jiān)視器鎖,這時候線程 2 就獲取不到了。然后線程 1 再去獲取 resource2 的監(jiān)視器鎖,可以獲取到。然后線程 1 釋放了對 resource1、resource2 的監(jiān)視器鎖的占用,線程 2 獲取到就可以執(zhí)行了。這樣就破壞了破壞循環(huán)等待條件,因此避免了死鎖。

      9. 說說 sleep() 方法和 wait() 方法區(qū)別和共同點?

      • 兩者最主要的區(qū)別在于:sleep 方法沒有釋放鎖,而 wait 方法釋放了鎖 。
      • 兩者都可以暫停線程的執(zhí)行。
      • Wait 通常被用于線程間交互/通信,sleep 通常被用于暫停執(zhí)行。
      • wait() 方法被調(diào)用后,線程不會自動蘇醒,需要別的線程調(diào)用同一個對象上的 notify() 或者 notifyAll() 方法。sleep() 方法執(zhí)行完成后,線程會自動蘇醒。或者可以使用wait(long timeout)超時后線程會自動蘇醒。

      10. 為什么我們調(diào)用 start() 方法時會執(zhí)行 run() 方法,為什么我們不能直接調(diào)用 run() 方法?

      這是另一個非常經(jīng)典的 java 多線程面試問題,而且在面試中會經(jīng)常被問到。很簡單,但是很多人都會答不上來!

      new 一個 Thread,線程進入了新建狀態(tài);調(diào)用 start() 方法,會啟動一個線程并使線程進入了就緒狀態(tài),當分配到時間片后就可以開始運行了。 start() 會執(zhí)行線程的相應準備工作,然后自動執(zhí)行 run() 方法的內(nèi)容,這是真正的多線程工作。 而直接執(zhí)行 run() 方法,會把 run 方法當成一個 main 線程下的普通方法去執(zhí)行,并不會在某個線程中執(zhí)行它,所以這并不是多線程工作。

      總結(jié): 調(diào)用 start 方法方可啟動線程并使線程進入就緒狀態(tài),而 run 方法只是 thread 的一個普通方法調(diào)用,還是在主線程里執(zhí)行。

      推薦教程:java教程

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號