<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 29 押寶線程源碼面試題 ## 引導語 關于線程方面的面試題,大部分都是概念題,我們需要大概的清楚這些概念,和面試官達成共識即可,本章我們一起來看下這些面試題,對前兩章的學習進行鞏固。 ### 1 面試題 #### 1.1 創建子線程時,子線程是得不到父線程的 ThreadLocal,有什么辦法可以解決這個問題? 答:這道題主要考察線程的屬性和創建過程,可以這么回答。 可以使用 InheritableThreadLocal 來代替 ThreadLocal,ThreadLocal 和 InheritableThreadLocal 都是線程的屬性,所以可以做到線程之間的數據隔離,在多線程環境下我們經常使用,但在有子線程被創建的情況下,父線程 ThreadLocal 是無法傳遞給子線程的,但 InheritableThreadLocal 可以,主要是因為在線程創建的過程中,會把 InheritableThreadLocal 里面的所有值傳遞給子線程,具體代碼如下: ``` // 當父線程的 inheritableThreadLocals 的值不為空時 // 會把 inheritableThreadLocals 里面的值全部傳遞給子線程 if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); ``` #### 1.2 線程創建有幾種實現方式? 答:主要有三種,分成兩大類,第一類是子線程沒有返回值,第二類是子線程有返回值。 無返回值的線程有兩種寫法,第一種是繼承 Thread,可以這么寫: ``` class MyThread extends Thread{ @Override public void run() { log.info(Thread.currentThread().getName()); } } @Test public void extendThreadInit(){ new MyThread().start(); } ``` 第二種是實現 Runnable 接口,并作為 Thread 構造器的入參,代碼如下: ``` Thread thread = new Thread(new Runnable() { @Override public void run() { log.info("{} begin run",Thread.currentThread().getName()); } }); // 開一個子線程去執行 thread.start(); ``` 這兩種都會開一個子線程去執行任務,并且是沒有返回值的,如果需要子線程有返回值,需要使用 Callable 接口,但 Callable 接口是無法直接作為 Thread 構造器的入參的,必須結合 FutureTask 一起使用,可以這樣寫代碼: ``` @Test public void testThreadByCallable() throws ExecutionException, InterruptedException { FutureTask futureTask = new FutureTask(new Callable<String> () { @Override public String call() throws Exception { Thread.sleep(3000); String result = "我是子線程"+Thread.currentThread().getName(); log.info("子線程正在運行:{}",Thread.currentThread().getName()); return result; } }); new Thread(futureTask).start(); log.info("返回的結果是 {}",futureTask.get()); } ``` 把 FutureTask 作為 Thread 的入參就可以了,FutureTask 組合了 Callable ,使我們可以使用 Callable,并且 FutureTask 實現了 Runnable 接口,使其可以作為 Thread 構造器的入參,還有 FutureTask 實現了 Future,使其對任務有一定的管理功能。 #### 1.3 子線程 1 去等待子線程 2 執行完成之后才能執行,如何去實現? 答:這里考察的就是 Thread.join 方法,我們可以這么做: ``` @Test public void testJoin2() throws Exception { Thread thread2 = new Thread(new Runnable() { @Override public void run() { log.info("我是子線程 2,開始沉睡"); try { Thread.sleep(2000L); } catch (InterruptedException e) { e.printStackTrace(); } log.info("我是子線程 2,執行完成"); } }); Thread thread1 = new Thread(new Runnable() { @Override public void run() { log.info("我是子線程 1,開始運行"); try { log.info("我是子線程 1,我在等待子線程 2"); // 這里是代碼關鍵 thread2.join(); log.info("我是子線程 1,子線程 2 執行完成,我繼續執行"); } catch (InterruptedException e) { e.printStackTrace(); } log.info("我是子線程 1,執行完成"); } }); thread1.start(); thread2.start(); Thread.sleep(100000); } ``` 子線程 1 需要等待子線程 2,只需要子線程 1 運行的時候,調用子線程 2 的 join 方法即可,這樣線程 1 執行到 join 代碼時,就會等待線程 2 執行完成之后,才會繼續執行。 #### 1.4 守護線程和非守護線程的區別?如果我想在項目啟動的時候收集代碼信息,請問是守護線程好,還是非守護線程好,為什么? 答:兩者的主要區別是,在 JVM 退出時,JVM 是不會管守護線程的,只會管非守護線程,如果非守護線程還有在運行的,JVM 就不會退出,如果沒有非守護線程了,但還有守護線程的,JVM 直接退出。 如果需要在項目啟動的時候收集代碼信息,就需要看收集工作是否重要了,如果不太重要,又很耗時,就應該選擇守護線程,這樣不會妨礙 JVM 的退出,如果收集工作非常重要的話,那么就需要非守護進程,這樣即使啟動時發生未知異常,JVM 也會等到代碼收集信息線程結束后才會退出,不會影響收集工作。 #### 1.5 線程 start 和 run 之間的區別。 答:調用 Thread.start 方法會開一個新的線程,run 方法不會。 #### 1.6 Thread、Runnable、Callable 三者之間的區別。 答:Thread 實現了 Runnable,本身就是 Runnable,但同時負責線程創建、線程狀態變更等操作。 Runnable 是無返回值任務接口,Callable 是有返回值任務接口,如果任務需要跑起來,必須需要 Thread 的支持才行,Runnable 和 Callable 只是任務的定義,具體執行還需要靠 Thread。 #### 1.7 線程池 submit 有兩個方法,方法一可接受 Runnable,方法二可接受 Callable,但兩個方法底層的邏輯卻是同一套,這是如何適配的。 答:問題考察點在于 Runnable 和 Callable 之間是如何轉化的,可以這么回答。 Runnable 和 Callable 是通過 FutureTask 進行統一的,FutureTask 有個屬性是 Callable,同時也實現了 Runnable 接口,兩者的統一轉化是在 FutureTask 的構造器里實現的,FutureTask 的最終目標是把 Runnable 和 Callable 都轉化成 Callable,Runnable 轉化成 Callable 是通過 RunnableAdapter 適配器進行實現的。 線程池的 submit 底層的邏輯只認 FutureTask,不認 Runnable 和 Callable 的差異,所以只要都轉化成 FutureTask,底層實現都會是同一套。 具體 Runnable 轉化成 Callable 的代碼和邏輯可以參考上一章,有非常詳細的描述。 #### 1.8 Callable 能否丟給 Thread 去執行? 答:可以的,可以新建 Callable,并作為 FutureTask 的構造器入參,然后把 FutureTask 丟給 Thread 去執行即可。 #### 1.9 FutureTask 有什么作用(談談對 FutureTask 的理解)。 答:作用如下: 1. 組合了 Callable,實現了 Runnable,把 Callable 和 Runnnable 串聯了起來。 2. 統一了有參任務和無參任務兩種定義方式,方便了使用。 3. 實現了 Future 的所有方法,對任務有一定的管理功能,比如說拿到任務執行結果,取消任務,打斷任務等等。 #### 1.10 聊聊對 FutureTask 的 get、cancel 方法的理解 答:get 方法主要作用是得到 Callable 異步任務執行的結果,無參 get 會一直等待任務執行完成之后才返回,有參 get 方法可以設定固定的時間,在設定的時間內,如果任務還沒有執行成功, 直接返回異常,在實際工作中,建議多多使用 get 有參方法,少用 get 無參方法,防止任務執行過慢時,多數線程都在等待,造成線程耗盡的問題。 cancel 方法主要用來取消任務,如果任務還沒有執行,是可以取消的,如果任務已經在執行過程中了,你可以選擇不取消,或者直接打斷執行中的任務。 兩個方法具體的執行步驟和原理見上一章節源碼解析。 #### 1.11 Thread.yield 方法在工作中有什么用? 答:yield 方法表示當前線程放棄 cpu,重新參與到 cpu 的競爭中去,再次競爭時,自己有可能得到 cpu 資源,也有可能得不到,這樣做的好處是防止當前線程一直霸占 cpu。 我們在工作中可能會寫一些 while 自旋的代碼,如果我們一直 while 自旋,不采取任何手段,我們會發現 cpu 一直被當前 while 循環占用,如果能預見 while 自旋時間很長,我們會設置一定的判斷條件,讓當前線程陷入阻塞,如果能預見 while 自旋時間很短,我們通常會使用 Thread.yield 方法,使當前自旋線程讓步,不一直霸占 cpu,比如這樣: ``` boolean stop = false; while (!stop){ // dosomething Thread.yield(); } ``` #### 1.12 wait()和sleep()的相同點和區別? 答: 相同點: 1. 兩者都讓線程進入到 TIMED_WAITING 狀態,并且可以設置等待的時間。 不同點: 1. wait 是 Object 類的方法,sleep 是 Thread 類的方法。 2. sleep 不會釋放鎖,沉睡的時候,其它線程是無法獲得鎖的,但 wait 會釋放鎖。 #### 1.13 寫一個簡單的死鎖 demo ``` // 共享變量 1 private static final Object share1 = new Object(); // 共享變量 2 private static final Object share2 = new Object(); @Test public void testDeadLock() throws InterruptedException { // 初始化線程 1,線程 1 需要在鎖定 share1 共享資源的情況下再鎖定 share2 Thread thread1 = new Thread(() -> { synchronized (share1){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (share2){ log.info("{} is run",Thread.currentThread().getName()); } } }); // 初始化線程 2,線程 2 需要在鎖定 share2 共享資源的情況下再鎖定 share1 Thread thread2 = new Thread(() -> { synchronized (share2){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (share1){ log.info("{} is run",Thread.currentThread().getName()); } } }); // 當線程 1、2 啟動后,都在等待對方鎖定的資源,但都得不到,造成死鎖 thread1.start(); thread2.start(); Thread.sleep(1000000000); } ``` ### 2 總結 線程章節算是中等難度,我們需要清楚線程的概念,線程如何初始化,線程的狀態變更等等問題,這些知識點都是線程池、鎖的基礎,學好線程后,再學習線程池和鎖就會輕松很多。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看