目錄
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
,但Callable
的call()
方法可以有返回值并且可以拋出異常。要執行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()
方法才能被喚醒。
所以,
BLOCKED
和WAITING
兩個狀態最大的區別有兩個:
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 同時運行多個線程的效率是否會高,取決于線程的類型和任務的性質。一般來說,有兩種類型的線程:
- CPU 密集型:CPU 密集型的線程主要進行計算和邏輯處理,需要占用大量的 CPU 資源。
- 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
包中的 Lock
和 Condition
接口提供了比 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,他們同時都想申請對方的資源,所以這兩個線程就會互相等待而進入死鎖狀態。
產生死鎖的四個必要條件:
- 互斥條件:該資源任意一個時刻只由一個線程占用。
- 請求與保持條件:一個線程因請求資源而阻塞時,對已獲得的資源保持不放。
- 不剝奪條件:線程已獲得的資源在未使用完之前不能被其他線程強行剝奪,只有自己使用完畢后才釋放資源。
- 循環等待條件:若干線程之間形成一種頭尾相接的循環等待資源關系。
?