久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放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. 站長(zhǎng)資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

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

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

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

      1. 什么是線程和進(jìn)程?

      1.1. 何為進(jìn)程?

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

      在 Java 中,當(dāng)我們啟動(dòng) main 函數(shù)時(shí)其實(shí)就是啟動(dòng)了一個(gè) JVM 的進(jìn)程,而 main 函數(shù)所在的線程就是這個(gè)進(jìn)程中的一個(gè)線程,也稱(chēng)主線程。

      如下圖所示,在 windows 中通過(guò)查看任務(wù)管理器的方式,我們就可以清楚看到 window 當(dāng)前運(yùn)行的進(jìn)程(.exe 文件的運(yùn)行)。

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

      1.2. 何為線程?

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

      Java 程序天生就是多線程程序,我們可以通過(guò) JMX 來(lái)看一下一個(gè)普通的 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 和線程名稱(chēng)信息         for (ThreadInfo threadInfo : threadInfos) {             System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());         }     } }

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

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

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

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

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

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

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

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

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

      總結(jié): 線程 是 進(jìn)程 劃分成的更小的運(yùn)行單位。線程和進(jìn)程最大的不同在于基本上各進(jìn)程是獨(dú)立的,而各線程則不一定,因?yàn)橥贿M(jìn)程中的線程極有可能會(huì)相互影響。線程執(zhí)行開(kāi)銷(xiāo)小,但不利于資源的管理和保護(hù);而進(jìn)程正相反

      下面是該知識(shí)點(diǎn)的擴(kuò)展內(nèi)容!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      先從總體上來(lái)說(shuō):

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

      再深入到計(jì)算機(jī)底層來(lái)探討:

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

      5. 使用多線程可能帶來(lái)什么問(wèn)題?

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

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

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

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

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

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

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

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

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

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

      7. 什么是上下文切換?

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

      概括來(lái)說(shuō)就是:當(dāng)前任務(wù)在執(zhí)行完 CPU 時(shí)間片切換到另一個(gè)任務(wù)之前會(huì)先保存自己的狀態(tài),以便下次再切換回這個(gè)任務(wù)時(shí),可以再加載這個(gè)任務(wù)的狀態(tài)。任務(wù)從保存到再加載的過(guò)程就是一次上下文切換

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

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

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

      8.1. 認(rèn)識(shí)線程死鎖

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

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

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

      下面通過(guò)一個(gè)例子來(lái)說(shuō)明線程死鎖,代碼模擬了上圖的死鎖的情況 (代碼來(lái)源于《并發(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 通過(guò) synchronized (resource1) 獲得 resource1 的監(jiān)視器鎖,然后通過(guò) Thread.sleep(1000);讓線程 A 休眠 1s 為的是讓線程 B 得到執(zhí)行然后獲取到 resource2 的監(jiān)視器鎖。線程 A 和線程 B 休眠結(jié)束了都開(kāi)始企圖請(qǐng)求獲取對(duì)方的資源,然后這兩個(gè)線程就會(huì)陷入互相等待的狀態(tài),這也就產(chǎn)生了死鎖。上面的例子符合產(chǎn)生死鎖的四個(gè)必要條件。

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

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

      8.2. 如何避免線程死鎖?

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

      破壞互斥條件

      這個(gè)條件我們沒(méi)有辦法破壞,因?yàn)槲覀冇面i本來(lái)就是想讓他們互斥的(臨界資源需要互斥訪問(wèn))。

      破壞請(qǐng)求與保持條件

      一次性申請(qǐng)所有的資源。

      破壞不剝奪條件

      占用部分資源的線程進(jìn)一步申請(qǐng)其他資源時(shí),如果申請(qǐng)不到,可以主動(dòng)釋放它占有的資源。

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

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

      我們對(duì)線程 2 的代碼修改成下面這樣就不會(huì)產(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)視器鎖,這時(shí)候線程 2 就獲取不到了。然后線程 1 再去獲取 resource2 的監(jiān)視器鎖,可以獲取到。然后線程 1 釋放了對(duì) resource1、resource2 的監(jiān)視器鎖的占用,線程 2 獲取到就可以執(zhí)行了。這樣就破壞了破壞循環(huán)等待條件,因此避免了死鎖。

      9. 說(shuō)說(shuō) sleep() 方法和 wait() 方法區(qū)別和共同點(diǎn)?

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

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

      這是另一個(gè)非常經(jīng)典的 java 多線程面試問(wèn)題,而且在面試中會(huì)經(jīng)常被問(wèn)到。很簡(jiǎn)單,但是很多人都會(huì)答不上來(lái)!

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

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

      推薦教程:java教程

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