【JUC面試篇】Java并發編程高頻八股——線程與多線程

目錄

1. 什么是進程和線程?有什么區別和聯系?

2.?Java的線程和操作系統的線程有什么區別?

3. 線程的創建方式有哪些?

4. 如何啟動和停止線程?

5. Java線程的狀態模型(有哪些狀態)?

6. 調用 interrupt 是如何讓線程拋出異常的?

7. 什么是線程上下文切換?

8. sleep 和 wait的區別是什么?

9. sleep會釋放cpu嗎?

10. 可以直接調用 Thread 類的 run 方法嗎?

11. blocked和waiting有什么區別?

12. wait 狀態下的線程如何進行恢復到 running 狀態?

13. notify 和 notifyAll 的區別?

14. 并發和并行的區別?

15. 同步和異步的區別?

16.?單核 CPU 支持 Java 多線程嗎?

17.?單核 CPU 上運行多個線程效率一定會高嗎?

18.?不同的線程之間如何通信?

19.?線程間通信方式有哪些?

20.?使用多線程要注意哪些問題?

21.?什么是線程死鎖?


1. 什么是進程和線程?有什么區別和聯系?

  • 進程:是操作系統進行資源分配和調度的基本單位。每個進程擁有獨立的內存空間。進程間相互隔離,一個進程崩潰通常不影響其他進程。

  • 線程:是進程內的執行單元是CPU調度的最小單位。同一進程的多個線程共享進程的內存空間和資源(如堆、全局變量),但每個線程有獨立的棧和程序計數器

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

2.?Java的線程和操作系統的線程有什么區別?

現在的 Java 線程的本質其實就是操作系統的線程

Java 線程采用的是一對一的線程模型,也就是一個 Java 線程對應一個系統內核線程。

3. 線程的創建方式有哪些?

1. 繼承Thread類

這是最直接的一種方式,用戶自定義類繼承java.lang.Thread類,重寫其run()方法,run()方法中定義了線程執行的具體任務。創建該類的實例后,通過調用start()方法啟動線程。

class MyThread extends Thread {@Overridepublic void run() {// 線程執行的代碼}
}public static void main(String[] args) {MyThread t = new MyThread();t.start();
}

采用繼承Thread類方式

  • 優點:編寫簡單,如果需要訪問當前線程,無需使用Thread.currentThread()方法,直接使用this,即可獲得當前線程
  • 缺點:因為線程類已經繼承了Thread類,所以不能再繼承其他的父類

2. 實現Runnable接口

如果一個類已經繼承了其他類,就不能再繼承Thread類,此時可以實現java.lang.Runnable接口。實現 Runnable接口需要重寫run()方法,然后將此Runnable對象作為參數傳遞給 Thread類的構造器,創建Thread對象后調用其start()方法啟動線程。

class MyRunnable implements Runnable {@Overridepublic void run() {// 線程執行的代碼}
}public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();
}

采用實現 Runnable接口方式:

  • 優點:線程類只是實現了Runable接口,還可以繼承其他的類。在這種方式下,可以多個線程共享同一個目標對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
  • 缺點:編程稍微復雜,如果需要訪問當前線程,必須使用Thread.currentThread( )方法。

?

3. 實現Callable接口與FutureTask

java.util.concurrent.Callable接口類似于Runnable,但Callablecall()方法可以有返回值并且可以拋出異常。要執行Callable任務,需將它包裝進一個FutureTask,因為Thread類的構造器只接受Runnable參數,而FutureTask實現了Runnable接口。

class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 線程執行的代碼,這里返回一個整型結果return 1;}
}public static void main(String[] args) {MyCallable task = new MyCallable();FutureTask<Integer> futureTask = new FutureTask<>(task);Thread t = new Thread(futureTask);t.start();try {Integer result = futureTask.get(); // 獲取線程執行結果System.out.println("Result: " + result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}
}

采用實現Callable接口方式:

  • 缺點:編程稍微復雜,如果需要訪問當前線程,必須調用Thread.currentThread()方法。
  • 優點:線程只是實現Runnable或實現Callable接口,還可以繼承其他類。這種方式下,多個線程可以共享一個 target 對象,非常適合多線程處理同一份資源的情形。

?

4. 使用線程池(Executor框架)

從Java 5開始引入的java.util.concurrent.ExecutorService和相關類提供了線程池的支持,這是一種更高效的線程管理方式,避免了頻繁創建和銷毀線程的開銷。可以通過Executors類的靜態方法創建不同類型的線程池。

class Task implements Runnable {@Overridepublic void run() {// 線程執行的代碼}
}public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(10); // 創建固定大小的線程池for (int i = 0; i < 100; i++) {executor.submit(new Task()); // 提交任務到線程池執行}executor.shutdown(); // 關閉線程池
}

采用線程池方式:

  • 缺點:線程池增加了程序的復雜度,特別是當涉及線程池參數調整和故障排查時。錯誤的配置可能導致死鎖、資源耗盡等問題,這些問題的診斷和修復可能較為復雜。
  • 優點:線程池可以重用預先創建的線程,避免了線程創建和銷毀的開銷,顯著提高了程序的性能。對于需要快速響應的并發請求,線程池可以迅速提供線程來處理任務,減少等待時間。并且,線程池能夠有效控制運行的線程數量,防止因創建過多線程導致的系統資源耗盡(如內存溢出)。通過合理配置線程池大小,可以最大化CPU利用率和系統吞吐量。

4. 如何啟動和停止線程?

啟動線程通過 Thread 類的 start()方法:

// 創建兩個線程,用 start 啟動線程
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();

停止線程的方法:

  • 異常法停止:線程調用interrupt()方法后,在線程的run方法中判斷當前對象的interrupted()狀態,如果是中斷狀態則拋出異常,達到中斷線程的效果。
  • 在沉睡中停止:先將線程sleep,然后調用interrupt標記中斷狀態,interrupt會將阻塞狀態的線程中斷。會拋出中斷異常,達到停止線程的效果
  • stop()暴力停止:線程調用stop()方法會被暴力停止,方法已棄用,該方法會有不好的后果:強制讓線程停止有可能使一些請理性的工作得不到完成。
  • 使用return停止線程:調用interrupt標記為中斷狀態后,在run方法中判斷當前線程狀態,如果為中斷狀態則return,能達到停止線程的效果。

5. Java線程的狀態模型(有哪些狀態)?

Java 線程在運行的生命周期中的指定時刻只可能處于下面 6 種不同狀態的其中一個狀態:

線程狀態解釋
NEW尚未啟動的線程狀態,即線程創建,還未調用start方法
RUNNABLE就緒狀態(調用start,等待調度)+正在運行
BLOCKED等待監視器鎖時,陷入阻塞狀態
WAITING等待狀態的線程正在等待另一線程執行特定的操作(如notify)
TIMED_WAITING具有指定等待時間的等待狀態
TERMINATED線程完成執行,終止狀態

可以調用線程 Thread中的getState()方法獲取當前線程的狀態。

詳細解釋

NEW

NEW?是線程的初始狀態。當通過new Thread()創建線程對象后,但尚未調用start()方法時,線程處于此狀態。此時,線程尚未分配系統資源,僅作為Java對象存在。需要強調的是,調用start()方法會觸發線程進入可運行狀態。

RUNNABLE

RUNNABLE?表示線程已啟動并準備好執行。調用start()方法后,線程進入此狀態。它可能正在運行(占用CPU)或就緒等待CPU調度(例如,在操作系統的就緒隊列中)。關鍵點在于,此狀態涵蓋了線程的實際執行和就緒等待,不區分兩者。如果線程在等待I/O操作(如網絡請求),它仍被視為RUNNABLE,因為底層操作系統可能處理了阻塞。

BLOCKED

BLOCKED?發生在線程嘗試獲取一個監視器鎖(monitor lock)時,但該鎖已被其他線程持有。典型場景是進入synchronized塊或方法時遇到鎖競爭。核心概念是線程在等待鎖釋放期間被阻塞,無法執行。一旦鎖可用,線程會自動轉換回RUNNABLE狀態。注意,這與等待狀態不同,BLOCKED是特定于鎖的競爭。

WAITING

WAITING?是線程無限期等待另一個線程執行特定操作的狀態。通過調用無參的Object.wait()Thread.join()LockSupport.park()方法進入此狀態。關鍵特征是線程不會主動喚醒,必須依賴其他線程的通知(如notify()notifyAll())才能恢復。這常用于線程間協調,但需小心避免死鎖。

TIMED_WAITING

TIMED_WAITING?類似于WAITING,但線程在指定時間內等待。通過調用帶超時參數的方法進入,如Thread.sleep(long millis)Object.wait(long timeout)Thread.join(long millis)核心區別在于,線程會在超時后自動返回RUNNABLE狀態,無需外部通知。這提供了更可控的等待機制,減少無限等待的風險。

TERMINATED

TERMINATED?是線程的終止狀態。當線程的run()方法正常執行完畢或拋出未捕獲異常時,進入此狀態。關鍵點是線程資源已被釋放,無法再重啟(調用start()會拋出異常)。此時,線程對象可能仍被引用,但已無執行能力。

6. 調用 interrupt 是如何讓線程拋出異常的?

每個線程都有一個與之關聯的布爾屬性來表示其中斷狀態,中斷狀態的初始值為false,當一個線程被其它線程調用 Thread.interrupt() 方法中斷時,會根據實際情況做出響應。

  • 如果該線程正在執行低級別 的可中斷方法(如?Thread.sleep()Thread.join()?或?Object.wait()),則會解除阻塞并拋出?InterruptedException?異常。
  • 否則?Thread.interrupt()?僅設置線程的中斷狀態,在該被中斷的線程中稍后可通過輪詢中斷狀態來決定是否要停止當前正在執行的任務。

7. 什么是線程上下文切換?

線程在執行過程中會有自己的運行條件和狀態(也稱上下文),比如上文所說到過的程序計數器,棧信息等。當出現如下情況的時候,線程會從占用 CPU 狀態中退出。

  • 主動讓出 CPU,比如調用了 sleep(), wait() 等。
  • 時間片用完,因為操作系統要防止一個線程或者進程長時間占用 CPU 導致其他線程或者進程餓死。
  • 調用了阻塞類型的系統中斷,比如請求 IO,線程被阻塞。
  • 被終止或結束運行

這其中前三種都會發生線程切換,線程切換意味著需要保存當前線程的上下文,留待線程下次占用 CPU 的時候恢復現場。并加載下一個將要占用 CPU 的線程上下文。這就是所謂的 上下文切換

上下文切換是現代操作系統的基本功能,因其每次需要保存信息恢復信息,這將會占用 CPU,內存等系統資源進行處理,也就意味著效率會有一定損耗,如果頻繁切換就會造成整體效率低下。

8. sleep 和 wait的區別是什么?

特性sleep()wait()
所屬類Thread類(靜態方法)Object類(實例方法)
鎖釋放??
使用前提任意位置調用必須在同步塊內(持有鎖)
喚醒機制超時自動恢復需 notify()/notifyAll() 或超時
設計用途暫停線程執行,不涉及鎖協作線程間協調,釋放鎖讓其他線程工作
  • 所屬分類的不同:sleep是 Thread類的靜態方法,可以在任何地方直接通過 Thread.sleep()調用,無需依賴對象實例。wait是 Object類的實例方法,這意味著必須通過對象實例來調用。
  • 鎖釋放的情況: Thread.sleep()在調用時,線程會暫停執行指定的時間,但不會釋放持有的對象鎖。也就是說,在 sleep期間,其他線程無法獲得該線程持有的鎖。 Object.wait():調用該方法時,線程會釋放持有的對象鎖,進入等待狀態,直到其他線程調用相同對象的 notify()或 notifyAll()方法喚醒它。
  • 使用條件:sleep可在任意位置調用,無需事先獲取鎖。wait必須在同步塊或同步方法內調用(即線程需持有該對象的鎖),否則拋出 IllegalMonitorStateException。
  • 喚醒機制:sleep休眠時間結束后,線程自動恢復到就緒狀態,等待CPU調度。wait需要其他線程調用相同對象的 notify()或 notifyAll()方法才能被喚醒。notify()會隨機喚醒一個在該對象上等待的線程,而 notifyAll()會喚醒所有在該對象上等待的線程。

9. sleep會釋放cpu嗎?

是的,調用 Thread.sleep() 時,線程會釋放 CPU,但不會釋放持有的鎖。

當線程調用 sleep() 后,會主動讓出 CPU 時間片,進入 TIMED_WAITING 狀態。此時操作系統會觸發調度,將 CPU 分配給其他處于就緒狀態的線程。這樣其他線程(無論是需要同一鎖的線程還是不相關線程)便有機會執行。

sleep() 不會釋放線程已持有的任何鎖(如 synchronized 同步代碼塊或方法中獲取的鎖)。因此,如果有其他線程試圖獲取同一把鎖,它們仍會被阻塞,直到原線程退出同步代碼塊。

10. 可以直接調用 Thread 類的 run 方法嗎?

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

總結:調用 start() 方法方可啟動線程并使線程進入就緒狀態,直接執行 run() 方法的話不會以多線程的方式執行。

11. blocked和waiting有什么區別?

區別如下:

  • 觸發條件:線程進入?BLOCKED?狀態通常是因為試圖獲取一個對象的鎖 (monitor lock) ,但該鎖已經被另一個線程持有。這通常發生在嘗試進入?synchronized?塊或方法時,如果鎖已被占用,則線程將被阻塞直到鎖可用。線程進入?WAITING?狀態是因為它正在等待另一個線程執行某些操作,例如調用?Object.wait()方法、?Thread.join()方法或?LockSupport.park()方法。在這種狀態下,線程將不會消耗 CPU 資源,并且不會參與鎖的競爭。

喚醒機制

當一個線程被阻塞等待鎖時,一旦鎖被釋放,線程將有機會重新嘗試獲取鎖。如果鎖此時未被其他線程獲取,那么線程可以從 BLOCKED 狀態變為 RUNNABLE 狀態。線程在 WAITING 狀態中需要被顯式喚醒。例如,如果線程調用了 Object.wait(),那么它必須等待另一個線程調用同一對象上的 Object.notify()Object.notifyAll() 方法才能被喚醒。

所以,BLOCKEDWAITING 兩個狀態最大的區別有兩個:

  • BLOCKED?是鎖競爭失敗后被被動觸發的狀態,WAITING?是人為的主動觸發的狀態
  • BLCKED?的喚醒是自動觸發的,而?WAITING?狀態是必須要通過特定的方法來主動喚醒

12. wait 狀態下的線程如何進行恢復到 running 狀態?

線程從等待(WAIT)狀態恢復到運行(RUNNING)狀態的核心機制是通過外部事件觸發或資源可用性變化,比如等待的線程被其他線程對象喚醒,notify() 和 notifyAll()。

synchronized (lock) {// 線程進入等待狀態,釋放鎖lock.wait();
}// 其他線程調用以下代碼喚醒等待線程
synchronized (lock) {lock.notify();     // 喚醒單個線程// lock.notifyAll(); // 喚醒所有等待線程
}

13. notify 和 notifyAll 的區別?

同樣是喚醒等待的線程,同樣最多只有一個線程能獲得鎖,同樣不能控制哪個線程獲得鎖。

區別在于:

  • notify:喚醒一個線程,其他線程依然處于wait的等待喚醒狀態,如果被喚醒的線程結束時沒調用notify,其他線程就永遠沒人去喚醒,只能等待超時,或者被中斷。

  • notifyAll:所有線程退出wait 的狀態,開始競爭鎖,但只有一個線程能搶到,這個線程執行完后,其他線程又會有一個幸運兒脫穎而出得到鎖。

notify在源碼的注釋中說到notify選擇喚醒的線程是任意的,但是依賴于具體實現的jvm。

14. 并發和并行的區別?

  • 并發:兩個及兩個以上的作業在同一?時間段?內執行。
  • 并行:兩個及兩個以上的作業在同一?時刻?執行。

最關鍵的點是:是否是?同時?執行。

15. 同步和異步的區別?

  • 同步:任務按順序依次執行,必須等待當前操作完成才能繼續下一個操作。
  • 異步:任務發起后無需等待完成,程序可繼續執行其他操作。

16.?單核 CPU 支持 Java 多線程嗎?

單核 CPU 是支持 Java 多線程的。操作系統通過時間片輪轉的方式,將 CPU 的時間分配給不同的線程。盡管單核 CPU 一次只能執行一個任務,但通過快速在多個線程之間切換,可以讓用戶感覺多個任務是同時進行的。

Java 使用的線程調度是搶占式的。也就是說,JVM 本身不負責線程的調度,而是將線程的調度委托給操作系統。操作系統通常會基于線程優先級和時間片來調度線程的執行,高優先級的線程通常獲得 CPU 時間片的機會更多。

17.?單核 CPU 上運行多個線程效率一定會高嗎?

單核 CPU 同時運行多個線程的效率是否會高,取決于線程的類型和任務的性質。一般來說,有兩種類型的線程:

  1. CPU 密集型:CPU 密集型的線程主要進行計算和邏輯處理,需要占用大量的 CPU 資源。
  2. IO 密集型:IO 密集型的線程主要進行輸入輸出操作,如讀寫文件、網絡通信等,需要等待 IO 設備的響應,而不占用太多的 CPU 資源。

在單核 CPU 上,同一時刻只能有一個線程在運行,其他線程需要等待 CPU 的時間片分配。如果線程是 CPU 密集型的,那么多個線程同時運行會導致頻繁的線程切換,增加了系統的開銷,降低了效率。如果線程是 IO 密集型的,那么多個線程同時運行可以利用 CPU 在等待 IO 時的空閑時間,提高了效率。

因此,對于單核 CPU 來說,如果任務是 CPU 密集型的,那么開很多線程會影響效率;如果任務是 IO 密集型的,那么開很多線程會提高效率。當然,這里的“很多”也要適度,不能超過系統能夠承受的上限。

18.?不同的線程之間如何通信?

?共享變量是最基本的線程間通信方式。多個線程可以訪問和修改同一個共享變量,從而實現信息的傳遞為了保證線程安全,通常需要使用 synchronized 關鍵字或 volatile 關鍵字。

class SharedVariableExample {// 使用 volatile 關鍵字保證變量的可見性private static volatile boolean flag = false;public static void main(String[] args) {// 生產者線程Thread producer = new Thread(() -> {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// 修改共享變量flag = true;System.out.println("Producer: Flag is set to true.");});// 消費者線程Thread consumer = new Thread(() -> {while (!flag) {// 等待共享變量被修改}System.out.println("Consumer: Flag is now true.");});producer.start();consumer.start();}
}

代碼解釋

  • volatile?關鍵字確保了?flag?變量在多個線程之間的可見性,即一個線程修改了?flag?的值,其他線程能立即看到。
  • 生產者線程在睡眠 2 秒后將?flag?設置為?true,消費者線程在?flag?為?false?時一直等待,直到?flag?變為?true?才繼續執行。

?

?Object?類中的?wait()notify()?和?notifyAll()?方法可以用于線程間的協作。wait()?方法使當前線程進入等待狀態,notify()?方法喚醒在此對象監視器上等待的單個線程,notifyAll()?方法喚醒在此對象監視器上等待的所有線程。

class WaitNotifyExample {private static final Object lock = new Object();public static void main(String[] args) {// 生產者線程Thread producer = new Thread(() -> {synchronized (lock) {try {System.out.println("Producer: Producing...");Thread.sleep(2000);System.out.println("Producer: Production finished. Notifying consumer.");// 喚醒等待的線程lock.notify();} catch (InterruptedException e) {e.printStackTrace();}}});// 消費者線程Thread consumer = new Thread(() -> {synchronized (lock) {try {System.out.println("Consumer: Waiting for production to finish.");// 進入等待狀態lock.wait();System.out.println("Consumer: Production finished. Consuming...");} catch (InterruptedException e) {e.printStackTrace();}}});consumer.start();producer.start();}
}

代碼解釋:

  • lock?是一個用于同步的對象,生產者和消費者線程都需要獲取該對象的鎖才能執行相應的操作。
  • 消費者線程調用?lock.wait()?方法進入等待狀態,釋放鎖;生產者線程執行完生產任務后調用?lock.notify()?方法喚醒等待的消費者線程。

?

java.util.concurrent.locks 包中的 LockCondition 接口提供了比 synchronized 更靈活的線程間通信方式。 Condition接口的await()方法類似于wait()方法,signal()方法類似于notify()方法,signalAll()方法類似于notifyAll()方法。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class LockConditionExample {private static final Lock lock = new ReentrantLock();private static final Condition condition = lock.newCondition();public static void main(String[] args) {// 生產者線程Thread producer = new Thread(() -> {lock.lock();try {System.out.println("Producer: Producing...");Thread.sleep(2000);System.out.println("Producer: Production finished. Notifying consumer.");// 喚醒等待的線程condition.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});// 消費者線程Thread consumer = new Thread(() -> {lock.lock();try {System.out.println("Consumer: Waiting for production to finish.");// 進入等待狀態condition.await();System.out.println("Consumer: Production finished. Consuming...");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});consumer.start();producer.start();}
}

代碼解釋:

  • ReentrantLock?是?Lock?接口的一個實現類,condition?是通過?lock.newCondition()?方法創建的。
  • 消費者線程調用?condition.await()?方法進入等待狀態,生產者線程執行完生產任務后調用?condition.signal()?方法喚醒等待的消費者線程。

?

java.util.concurrent 包中的 BlockingQueue 接口提供了線程安全的隊列操作,當隊列滿時,插入元素的線程會被阻塞;當隊列為空時,獲取元素的線程會被阻塞。

隊列自動處理線程阻塞與喚醒(如隊列空時阻塞消費者,滿時阻塞生產者)。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;class BlockingQueueExample {private static final BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(1);public static void main(String[] args) {// 生產者線程Thread producer = new Thread(() -> {try {System.out.println("Producer: Producing...");queue.put(1);System.out.println("Producer: Production finished.");} catch (InterruptedException e) {e.printStackTrace();}});// 消費者線程Thread consumer = new Thread(() -> {try {System.out.println("Consumer: Waiting for production to finish.");int item = queue.take();System.out.println("Consumer: Consumed item: " + item);} catch (InterruptedException e) {e.printStackTrace();}});consumer.start();producer.start();}
}

代碼解釋:

  • LinkedBlockingQueue 是 BlockingQueue 接口的一個實現類,容量為 1。
  • 生產者線程調用 queue.put(1) 方法將元素插入隊列,如果隊列已滿,線程會被阻塞;消費者線程調用 queue.take() 方法從隊列中取出元素,如果隊列為空,線程會被阻塞。

19.?線程間通信方式有哪些?

1. 共享內存(隱式通信)

線程通過共享變量(如對象成員變量、靜態變量)隱式通信。
關鍵點:必須使用?synchronized?或?volatile?保證可見性與原子性
示例:volatile?確保變量修改后立即對其他線程可見,synchronized?通過鎖機制實現原子操作和內存屏障。

2.?wait()/notify()(顯式同步)

基于?Object?內置鎖的經典通信方式:

  • wait():釋放鎖并阻塞線程,需在?synchronized?塊內調用

  • notify()/notifyAll():喚醒同一鎖上等待的線程(隨機一個或全部)。

synchronized(lock) {while(條件不滿足) { // 必須用循環檢查條件lock.wait();    // 釋放鎖,線程阻塞}// 執行業務邏輯lock.notifyAll();   // 喚醒其他線程
}

3.?Lock?與?Condition(顯式鎖)

使用?java.util.concurrent.locks?包下的更靈活機制:

  • Lock?替代?synchronized,提供?lock()/unlock()?顯式鎖控制。

  • Condition?替代?wait()/notify(),支持多個等待隊列

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();lock.lock();
try {while(條件不滿足) {condition.await(); // 釋放鎖并等待}condition.signalAll(); // 喚醒等待線程
} finally {lock.unlock();
}

4. 阻塞隊列(BlockingQueue

生產者-消費者模型的首選方案:

  • 隊列自動處理線程阻塞與喚醒(如隊列空時阻塞消費者,滿時阻塞生產者)。

  • 常用實現類:ArrayBlockingQueue,?LinkedBlockingQueue

BlockingQueue<String> queue = new ArrayBlockingQueue<>(10);// 生產者
queue.put("data"); // 隊列滿時自動阻塞// 消費者
String data = queue.take(); // 隊列空時自動阻塞

5. 同步工具類(CountDownLatch/CyclicBarrier

控制線程執行順序的高級工具:

  • CountDownLatch:線程等待指定次數的事件完成(一次性)。

CountDownLatch latch = new CountDownLatch(3);
// 工作線程
latch.countDown(); // 事件完成,計數器減1
// 主線程
latch.await();    // 阻塞直到計數器歸零
  • CyclicBarrier:線程互相等待到達屏障點(可重復使用)。
CyclicBarrier barrier = new CyclicBarrier(3);
// 工作線程
barrier.await(); // 阻塞直到所有線程到達

20.?使用多線程要注意哪些問題?

要保證多線程的程序是安全,不要出現數據競爭造成的數據混亂的問題。

Java的線程安全在三個方面體現∶

  • 原子性:提供互斥訪問, 同一時刻只能有一個線程對數據進行操作,在Java中使用了atomic包(這個包提供了一些支持原子操作的類,這些類可以在多線程環境下保證操作的原子性)和synchronized關鍵字來確保原子性;
  • 可見性:一個線程對主內存的修改可以及時地被其他線程看到, 在Java中使用了synchronized和volatile這兩個關鍵字確保可見性;
  • 有序性:一個線程觀察其他線程中的指令執行順序, 由于指令重排序,該觀察結果一般雜亂無序,在Java中使用了happens - before原則來確保有序性。

21.?什么是線程死鎖?

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

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

產生死鎖的四個必要條件:

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

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86709.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86709.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86709.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LSTM-SVM多變量時序預測(Matlab完整源碼和數據)

LSTM-SVM多變量時序預測&#xff08;Matlab完整源碼和數據&#xff09; 目錄 LSTM-SVM多變量時序預測&#xff08;Matlab完整源碼和數據&#xff09;效果一覽基本介紹程序設計參考資料 效果一覽 基本介紹 代碼主要功能 該代碼實現了一個LSTM-SVM多變量時序預測模型&#xff0c…

ES6——數組擴展之Set數組

在ES6&#xff08;ECMAScript 2015&#xff09;中&#xff0c;JavaScript的Set對象提供了一種存儲任何值唯一性的方式&#xff0c;類似于數組但又不需要索引訪問。這對于需要確保元素唯一性的場景非常有用。Set對象本身并不直接提供數組那樣的方法來操作數據&#xff08;例如ma…

日志收集工具-logstash

提示&#xff1a;Windows 環境下 安裝部署 logstash 采集日志文件 文章目錄 一、下載二、解壓部署三、常用插件四、常用配置 Logstash 服務器數據處理管道&#xff0c;能夠從多個來源采集數據&#xff0c;轉換數據&#xff0c;然后將數據發送到您最喜歡的存儲庫中。Logstash 沒…

6個月Python學習計劃 Day 21 - Python 學習前三周回顧總結

? 第一周&#xff1a;基礎入門與流程控制&#xff08;Day 1 - 7&#xff09; “打地基”的一周&#xff0c;我們走完了從變量、輸入輸出、判斷、循環到第一個小型系統的完整鏈路。 &#x1f4d8; 學習重點&#xff1a; Python 基礎語法&#xff1a;變量類型、字符串格式化、注…

Spring Boot SQL數據庫功能詳解

Spring Boot自動配置與數據源管理 數據源自動配置機制 當在Spring Boot項目中添加數據庫驅動依賴&#xff08;如org.postgresql:postgresql&#xff09;后&#xff0c;應用啟動時自動配置系統會嘗試創建DataSource實現。開發者只需提供基礎連接信息&#xff1a; 數據庫URL格…

java每日精進 6.11【消息隊列】

1.內存級Spring_Event 1.1 控制器層&#xff1a;StringTextController /*** 字符串文本管理控制器* 提供通過消息隊列異步獲取文本信息的接口*/ RestController RequestMapping("/api/string-text") public class StringTextController {Resourceprivate StringTex…

【凌智視覺模塊】rv1106 部署 ppocrv4 檢測模型 rknn 推理

PP-OCRv4 文本框檢測 1. 模型介紹 如有需要可以前往我們的倉庫進行查看 凌智視覺模塊 PP-OCRv4在PP-OCRv3的基礎上進一步升級。整體的框架圖保持了與PP-OCRv3相同的pipeline&#xff0c;針對檢測模型和識別模型進行了數據、網絡結構、訓練策略等多個模塊的優化。 從算法改…

uniapp Vue2 獲取電量的獨家方法:繞過官方插件限制

在使用 uniapp 進行跨平臺應用開發時&#xff0c;獲取設備電量信息是一個常見的需求。然而&#xff0c;uniapp 官方提供的uni.getBatteryInfo方法存在一定的局限性&#xff0c;它不僅需要下載插件&#xff0c;而且目前僅支持 Vue3&#xff0c;這讓使用 Vue2 進行開發的開發者陷…

Go語言中的if else控制語句

if else是Go語言中最基礎也最常用的條件控制語句&#xff0c;用于根據條件執行不同的代碼塊。下面我將詳細介紹Go語言中if else的各種用法和特性。 1. 基本語法 1.1. 最簡單的if語句 if 條件表達式 {// 條件為true時執行的代碼 } 示例&#xff1a; if x > 10 {fmt.Prin…

[Spring]-AOP

AOP場景 AOP: Aspect Oriented Programming (面向切面編程) OOP: Object Oriented Programming (面向對象編程) 場景設計 設計: 編寫一個計算器接口和實現類&#xff0c;提供加減乘除四則運算 需求: 在加減乘除運算的時候需要記錄操作日志(運算前參數、運算后結果)實現方案:…

Web3 借貸與清算機制全解析:鏈上金融的運行邏輯

Web3 借貸與清算機制全解析&#xff1a;鏈上金融的運行邏輯 超額抵押借款 例如&#xff0c;借款人用ETH為抵押借入DAI&#xff1b;借款人的ETH的價值一定是要超過DAI的價值&#xff1b;借款人可以任意自由的使用自己借出的DAI 穩定幣 第一步&#xff1a;借款人需要去提供一定…

RK3588開發筆記-GNSS-RTK模塊調試

目錄 前言 一、什么是GNSS/RTK 二、硬件連接 三、內核配置 四、模塊調試 五、ntripclient使用 總結 前言 在RK3588平臺上集成高精度定位功能是許多工業級應用的需求。本文記錄了我調試GNSS-RTK模塊的全過程,包含硬件連接、驅動移植、數據解析和精度優化等關鍵環節,希望對…

Vue.js $emit的介紹和簡單使用

前言 在 Vue.js 開發中&#xff0c;組件化是核心思想之一。但組件間的通信是一個重要課題&#xff0c;特別是子組件向父組件傳遞數據的場景。Vue 提供了多種通信方式&#xff0c;而$emit正是實現子→父通信的關鍵方法。本文將深入解析$emit的原理、使用場景及最佳實踐。 一、$e…

【Linux 學習計劃】-- 簡易版shell編寫

目錄 思路 創建自己的命令行 獲取用戶命令 分割命令 檢查是否是內建命令 cd命令實現 進程程序替換執行程序 總代碼 結語 思路 int main() {while (1){// 1. 自己的命令行PrintCommandLine();// 2. 獲取用戶命令char command[SIZE];int n GetUserCommand(command, si…

一個完整的日志收集方案:Elasticsearch + Logstash + Kibana+Filebeat (二)

&#x1f4c4; 本地 Windows 部署 Logstash 連接本地 Elasticsearch 指南 ? 目標 在本地 Windows 上安裝并運行 Logstash配置 Logstash 將數據發送至本地 Elasticsearch測試數據采集與 ES 存儲流程 &#x1f9f0; 前提條件 軟件版本要求安裝說明Java17Oracle JDK 下載 或 O…

Java使用Selenium反爬蟲優化方案

當我們爬取大站的時候&#xff0c;就得需要對抗反爬蟲機制的場景&#xff0c;因為項目要求使用Java和Selenium。Selenium通常用于模擬用戶操作&#xff0c;但效率較低&#xff0c;所以需要我們結合其他技術來實現高效。 在 Java 中使用 Selenium 進行高效反爬蟲對抗時&#xff…

狀態管理方案對比與決策

1. 狀態管理的基本概念 現代前端應用隨著功能復雜度提升&#xff0c;狀態管理已成為架構設計的核心挑戰。狀態管理本質上解決的是數據的存儲、變更追蹤和響應式更新問題&#xff0c;以確保UI與底層數據保持同步。 核心挑戰: 狀態共享與組件通信可預測的狀態變更性能優化與重…

Fetch與Axios:區別、聯系、優缺點及使用差異

Fetch與Axios&#xff1a;區別、聯系、優缺點及使用差異 文章目錄 Fetch與Axios&#xff1a;區別、聯系、優缺點及使用差異一、聯系二、區別1. 瀏覽器支持與兼容性2. 響應處理3. 請求攔截和響應攔截4. 錯誤處理 三、優缺點1. Fetch API優點缺點 2. Axios優點缺點 四、使用上的差…

【Docker】快速入門與項目部署實戰

我們在部署一個項目時&#xff0c;會出現一系列問題比如&#xff1a; 命令太多了&#xff0c;記不住軟件安裝包名字復雜&#xff0c;不知道去哪里找安裝和部署步驟復雜&#xff0c;容易出錯 其實上述問題不僅僅是新手&#xff0c;即便是運維在安裝、部署的時候一樣會覺得麻煩…

Java面試題尚硅谷版第1季

1、寫出如下代碼運行結果 1.1、 使用局部變量表和操作數棧解題 1.2、使用前置和后置遞增解題 2、寫一個單例模式 2.1、考察知識點 2.2、單例模式實現 3、類加載和初始化順序 package classload;public class Father {private int i test();private static int j method();st…