【Java】線程&同步 - 練習面試題

線程&同步 - 練習面試題

線程概念

進程(Process)

程序的一次運行。操作系統會給這個進程分配資源(例如:內存),進程與進程之間的內存是獨立,無法直接共享。最早的DOS操作系統是單任務的,同一時間只能運行一個進程。後來現在的操作系統都是支持多任務的,可以同時運行多個進程。兩個進程之間進行來回切換,通信(交換數據)等操作時,成本比較高。
總的來說進程是一個具有一定獨立功能的程序在一個數據集上的一次動態執行的過程,是操作系統進行資源分配和調度的一個獨立單位,是應用程序運行的載體。

什麼是進程?

進程是一個具有一定獨立功能的程序在一個數據集上的一次動態執行的過程,是操作系統進行資源分配和調度的一個獨立單位,是應用程序運行的載體 程序的一次運行 OS進行資源分配和調度的單位 應用程序運行的載體

什麼是線程?

  • 線程是操作系統能夠進行運算調度的最小單位。

  • 它被包含在進程之中,是進程中的實際運作單位。

  • 一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程並行執行不同的任務。

線程是進程中的其中一條執行路徑。一個進程中至少有一個線程,也可以有多個線程。有的時候也把線程稱爲輕量級的進程。同一個進程的多個線程之間有些內存是可以共享的(方法區、堆),也有些內存是獨立的(棧(包括虛擬機棧和本地方法棧)、程序計數器)。 因爲線程之間可能使用共享內存,那麼在數據交換成本上就比較低。而且線程之間的切換相對進程對於CPU和操作系統來說,成本比較低。

進程和線程的區別?

進程和線程的主要差別在於它們是不同的操作系統資源管理方式。

進程有獨立的地址空間,一個進程崩潰後,在保護模式下不會對其它進程產生影響。

線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。

但對於一些要求同時進行並且又要共享某些變量的併發操作,只能用線程,不能用進程。

java 中線程有哪些狀態?

共 6 種狀態:

  • 初始狀態 (NEW) :尚未啓動的線程處於此狀態。通常是新創建了線程,但還沒有調用 start () 方法;
  • 運行狀態 (RUNNABLE):Java 線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱爲 “運行中”。比如說線程可運行線程池中,等待被調度選中,獲取 CPU 的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得 CPU 時間片後變爲運行中狀態(running)。
  • 阻塞狀態 (BLOCKED):表示線程阻塞於鎖;
  • 等待狀態 (WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷);
  • 超時等待狀態 (TIMED_WAITING):進入該狀態的線程需要等待其他線程在指定時間內做出一些特定動作(通知或中斷),可以在指定的時間自行返回;
  • 終止狀態 (TERMINATED):表示該線程已經執行完畢,已退出的線程處理此狀態。

新建/出生、就緒、運行、阻塞、死亡

多線程和單線程有什麼區別?

單線程程序:程序執行過程中只有一個有效操作的序列,不同操作之間都有明確的執行先後順序,容易出現代碼阻塞

多線程程序:有多個線程,線程間獨立運行,能有效地避免代碼阻塞,並且提高程序的運行性能

爲什麼要使用多線程?

1、使用多線程可以減少程序的響應時間。 在單線程的情況下,如果某個程序很耗時或者陷入長時間等待(如等待網絡響應),此時程序將不會相應鼠標和鍵盤等操作,使用多線程後,可以把這個耗時的線程分配到一個單獨的線程去執行,從而是程序具備了更好的交互性。

2、與進程相比,線程的創建和切換開銷更小。 由於啓動一個新的線程必須給這個線程分配獨立的地址空間,建立許多數據結構來維護線程代碼段、數據段等信息,而運行於同一個進程內的線程共享代碼段、數據段,線程的啓動或切換的開銷就比進程要少很多。同時多線程在數據共享方面效率非常高。

3、多CPU或多核心計算機本身就具有執行多線程的能力。 如果使用單個線程,將無法重複利用計算機資源,造成資源的巨大浪費。因此在多CPU計算機上使用多線程能提高CPU的利用率。

4、使用多線程能簡化程序的結構,使用程序便於理解和維護。 一個非常複雜的進程可以分成多個線程來執行。

什麼是線程安全?

當多個線程訪問同一個對象時,如果不用考慮這些線程在運行時環境下的調度和交替運行,也不需要進行額外的同步,或者在調用方進行任何其他的協調操作,調用這個對象的行爲都可以獲取正確的結果,那這個對象是線程安全的。

爲何要使用線程同步?

Java允許多線程併發控制,當多個線程同時操作一個可共享的資源變量時(如數據的增刪改查),將會導致數據不準確,相互之間產生衝突。

因此加入同步鎖以避免在該線程沒有完成操作之前,被其他線程的調用,從而保證了該變量的唯一性和準確性。

如何確保線程安全?

1、對非安全的代碼進行加鎖控制;

2、使用線程安全的類;

3、多線程併發情況下,線程共享的變量改爲方法級的局部變量

線程安全的級別?

1、不可變。不可變的對象一定是線程安全的,並且永遠也不需要額外的同步。

Java類庫中大多數基本數值類如Integer、String和BigInteger都是不可變的。

2、無條件的線程安全。由類的規格說明所規定的約束在對象被多個線程訪問時仍然有效,不管運行時環境如何排列,線程都不需要任何額外的同步。

如 Random 、ConcurrentHashMap、Concurrent集合、atomic

3、有條件的線程安全。有條件的線程安全類對於單獨的操作可以是線程安全的,但是某些操作序列可能需要外部同步。

有條件線程安全的最常見的例子是遍歷由 Hashtable 或者 Vector 或者返回的迭代器

4、非線程安全(線程兼容)。線程兼容類不是線程安全的,但是可以通過正確使用同步而在併發環境中安全地使用。

如ArrayList HashMap

5、線程對立。線程對立是那些不管是否採用了同步措施,都不能在多線程環境中併發使用的代碼。

如如System.setOut()、System.runFinalizersOnExit()

爲什麼我們調用 start() 方法時會執行 run() 方法,爲什麼我們不能直接調用 run() 方法?

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

總結: 調用 start 方法方可啓動線程並使線程進入就緒狀態,而 run 方法只是 thread 的一個普通方法調用,還是在主線程裏執行。

使用多線程可能帶來什麼問題?

多線程的目的就是爲了能提高程序的執行效率提高程序運行速度,但也可能會遇到很多問題:內存泄漏、上下文切換、死鎖還有受限於硬件和軟件的資源閒置問題。

實現Runnable接口和Callable接口的區別?

兩者的區別在於 Runnable 接口不會返回結果但是 Callable 接口可以返回結果。

備註: 工具類Executors可以實現Runnable對象和Callable對象之間的相互轉換。(Executors.callable(Runnable task)或Executors.callable(Runnable task,Object resule))。

創建線程有哪些方式?

1、繼承 Thread 類創建線程類;

2、通過 Runnable 接口創建線程類。

3、通過 Callable 和 Future 創建線程。

4、線程池

練習一:多線程開啓

問題:
請描述Thread類中的start()方法與run()方法的區別。

答:
線程對象調用run()方法不開啓線程,僅是對象調用方法。

線程對象調用start()方法開啓線程,並讓jvm調用run()方法在開啓的線程中執行。

啓動線程的就是start()方法異步執行,run()方法就是普通方法調用同步執行。

練習二:創建多線程

問題:
請描述創建線程的兩種方式。

答:
第一種方式是將類聲明爲 Thread 的子類。

  1. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,因此把run()方法稱爲線程執行體。

  2. 創建Thread子類的實例,即創建了線程對象。

  3. 調用線程對象的start()方法來啓動該線程。

    start()方法

第二種方式是聲明一個類實現Runnable 接口。

  1. 定義Runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
  2. 創建Runnable實現類的實例,並以此實例作爲Thread的target來創建Thread對象,Thread對象纔是真正的線程對象。
  3. 調用線程對象的start()方法來啓動線程。

練習三:多線程練習

問題:

​ 請編寫程序,分別打印主線程的名稱和子線程的名稱。

​ 要求使用兩種方式實現:

​ 第一種方式:繼承Thread類。

​ 第二種方法:實現Runnable接口。

答:

操作步驟描述

l 第一種方式:繼承Thread類

​ 1.定義一個子線程的類,繼承Thread類;

​ 2.在子線程類中重寫run方法,在run方法中打印子線程的名稱;

​ 3.定義一個測試類;

​ 4.在main方法中打印主線程的名稱;

​ 5.在main方法中創建子線程對象;

​ 6.調用子線程對象的start方法,開啓子線程;

l 第二種方式:實現Runnable接口

1.定義一個子任務類,實現Runnable接口;

2.在子任務類中重寫run方法,在run方法中打印子線程的名稱;

3.定義一個測試類;

4.在main方法中打印主線程的名稱;

5.在main方法中創建一個子任務對象;

6.在main方法中創建一個Thread類的對象,並把子任務對象傳遞給Thread類的構造方法;

7.調用Thread類對象的start方法開啓子線程;

操作步驟答案

l 第一種方式:繼承Thread類

SubThread.java

/*
 * 1.定義一個子線程的類,繼承Thread類;
 */
public class SubThread extends Thread {
    /*
     *2.在子線程類中重寫run方法,在run方法中打印子線程的名稱;
     */
    public void run() {
        // 打印子線程的名稱
        System.out.println("subThread:" + Thread.currentThread().getName());
    }
}

ThreadDemo.java

/*
 * 3.定義一個測試類
 */
public class ThreadDemo {
    public static void main(String[] args) {
        // 4.在main方法中打印主線程的名稱;
        System.out.println("main:" + Thread.currentThread().getName());
        // 5.在main方法中創建子線程對象;
        SubThread st = new SubThread();
        // 6.調用子線程對象的start方法,開啓子線程。
        st.start();
    }
}


l 第二種方式:實現Runnable接口

SubRunnable.java

/*
 * 1.定義一個子任務類,實現Runnable接口。
 */
public class SubRunnable implements Runnable {
    @Override
    public void run() {
        // 2.在子任務類中重寫run方法,在run方法中打印子線程的名稱。
        System.out.println("SubRunnable:" + Thread.currentThread().getName());
    }
}

RunnableDemo

/*
 * 3.定義一個測試類。
 */
public class RunnableDemo {
    public static void main(String[] args) {
        // 4.在main方法中打印主線程的名稱。
        System.out.println("RunnableDemo:" + Thread.currentThread().getName());
        // 5.在main方法中創建一個子任務對象。
        SubRunnable r = new SubRunnable();
        // 6.在main方法中創建一個Thread類的對象,並把子任務對象傳遞給Thread類的                         構造方法。
        Thread t = new Thread(r);
        // 7.調用Thread類對象的start方法開啓子線程。
        t.start();
        //RunnableDemo:main
        //SubRunnable:Thread-0
    }
}

練習四:實現Runnable接口的優勢

問題:

請描述實現Runnable接口比繼承Thread類所具有的優勢:

答:

  1. 適合多個相同的程序代碼的線程去共享同一個資源。

  2. 可以避免java中的單繼承的侷限性。

  3. 增加程序的健壯性,實現解耦操作,代碼可以被多個線程共享,代碼和數據獨立。

  4. 線程池只能放入實現Runable或callable類線程,不能直接放入繼承Thread的類。

練習五:線程狀態

問題:請描述在線程的生命週期中, 有幾種狀態呢 ?

答:

  1. NEW(新建) 線程剛被創建,但是並未啓動。

  2. Runnable(可運行)

線程可以在java虛擬機中運行的狀態,可能正在運行自己代碼,也可能沒有,這取決於操作系統處理器。

  1. Blocked(鎖阻塞)

當一個線程試圖獲取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進入Blocked狀態;當該線程持有鎖時,該線程將變成Runnable狀態。

  1. Waiting(無限等待)

一個線程在等待另一個線程執行一個(喚醒)動作時,該線程進入Waiting狀態。進入這個狀態後是不能自動喚醒的,必須等待另一個線程調用notify或者notifyAll方法才能夠喚醒。

  1. Timed Waiting(計時等待)

同waiting狀態,有幾個方法有超時參數,調用他們將進入Timed Waiting狀態。這一狀態將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的常用方法有Thread.sleep 、Object.wait。

  1. Teminated(被終止)

因爲run方法正常退出而死亡,或者因爲沒有捕獲的異常終止了run方法而死亡。

問題 1:java 中線程有哪些狀態?

參考答案:

共 6 種狀態:

  • 初始狀態 (NEW) :尚未啓動的線程處於此狀態。通常是新創建了線程,但還沒有調用 start () 方法;
  • 運行狀態 (RUNNABLE):Java 線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱爲 “運行中”。比如說線程可運行線程池中,等待被調度選中,獲取 CPU 的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得 CPU 時間片後變爲運行中狀態(running)。
  • 阻塞狀態 (BLOCKED):表示線程阻塞於鎖;
  • 等待狀態 (WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷);
  • 超時等待狀態 (TIMED_WAITING):進入該狀態的線程需要等待其他線程在指定時間內做出一些特定動作(通知或中斷),可以在指定的時間自行返回;
  • 終止狀態 (TERMINATED):表示該線程已經執行完畢,已退出的線程處理此狀態。

記錄

1、 搞定Java核心技術

2、高薪之路–Java面試題精選集

3、【Java】Java->JavaWeb->Spring全家桶->社區、教育、電商項目等等

從Hello到goodbye

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章