Java并發編程面試題53道-JUC

Java中的JUC是"Java Concurrency Utilities"的縮寫,它是指Java平臺從Java 5版本開始引入的一系列用于處理多線程并發編程的工具類和框架。這個包(java.util.concurrent)極大地增強了Java在并發編程領域的支持,提供了一系列高級抽象如線程池(ThreadPoolExecutor)、并發集合(ConcurrentHashMap、CopyOnWriteArrayList等)、同步器(Semaphore、CountDownLatch、CyclicBarrier、ReentrantLock等)以及其他并發工具和框架。通過使用JUC,開發者能夠更加方便地設計出高效且線程安全的代碼,同時減少了編寫低級并發控制結構的復雜性,并有助于避免常見的并發錯誤,比如死鎖和競態條件。JUC讓Java程序員可以更專注于業務邏輯的并發實現,而不用過多關注底層并發控制機制的實現細節。

目錄

1.Java中線程的創建方式?

2.什么是線程池,有哪幾種常用的線程池?

3.線程的生命周期?

4.終止線程的四種方式?

5.sleep()與wait()方法的區別?

6.start()與run()的區別?

7.什么是線程安全?java中如何保證線程安全?Vector是一個線程安全類嗎?

8.什么是volatile關鍵字?它有什么作用?它的實現原理?

9.什么是死鎖?如何避免死鎖?

10.什么是CAS操作?底層原理?它有什么特點(優缺點)?

11.什么是線程間通信?如何實現線程間通信?

12.線程、進程、協程、程序的區別?

13.什么是原子操作?Java中如何保證原子操作?

14.什么是線程的上下文切換?如何減少上下文切換的開銷?

15.什么是線程安全的集合?Java中有哪些線程安全的集合類?

16.說一下JMM(Java內存模型)?再介紹一下JVM的內存模型?

17.如何創建線程池,線程池中 sumbit() 和 execute() 方法有什么區別?線程池的7大參數?

18.為什么使用線程池,線程池的底層原理?

19.線程池的四種拒絕策略?

20.什么是 AQS 嗎?了解 AQS 共享資源的方式嗎?

21.sychronized的底層原理?鎖升級過程?

22.說說 AtomicInteger 和 synchronized 的異同點?

23.原子類和 volatile 有什么異同?

24.什么是CountDownLatch、CyclicBarrier、Semaphore?

25.CountDownLatch、CyclicBarrier、Semaphore的區別?

26.synchronized 和 lock 有什么區別?synchronized 和 Lock 如何選擇?Lock接口的主要方法?

27.tryLock、lock和lockInterruptibly的區別?

28.什么是阻塞隊列?列舉幾個常見的阻塞隊列?什么是非阻塞隊列?

29.什么是ThreadLocal,它是線程安全的嗎?底層原理是什么?會存在內存泄露嗎?

30.HashTable、HashMap、ConcurrentHashMap有什么區別?

31、什么是樂觀瑣,什么是悲觀鎖,什么是公平鎖,什么是非公平瑣?

32、java中常見的四種引用類型?

33、java線程中涉及到的常見方法及其意義?線程基本方法 、線程等待(wait) 、線程睡眠(sleep) 線程讓步(yield) 線程中斷(interrupt) 、Join等待其他線程終止 、為什么要用join()方法?

34、并發編程的三要素?

35、ReentrantLock與sychronized的區別?實現原理?使用場景?

36、介紹一下ReentrantReadwriteLock?

37、并發編程解決生產者與消費者模型的常用的幾種方式?

38、什么是可重入瑣與不可重入瑣?

39、什么是共享鎖與排它鎖?

40、什么是自旋鎖?

41、Java中Runnable和Callable有什么不同?

42、什么是FutureTask?

43、如何在Java中獲取線程堆棧?JVM中哪個參數是用來控制線程的棧堆棧小的?

44、Thread類中的yield方法有什么作用?

45、有三個線程T保它1,T2,T3,怎么確們按順序執行?

46、一個線程運行時發生異常會怎樣? java中?如何在兩個線程間共享數據?

?47、?Java中notify 和 notifyAll有什么區別? 為什么wait, notify 和 notifyAll這些方法不在thread類里面?

48、Java中堆和棧有什么不同?

49、Java中活鎖和死鎖有什么區別? ?怎么檢測一個線程是否擁有鎖?

50、AQS使用了哪些設計模式?AQS 組件了解嗎?

51、線程池有哪幾種工作隊列?

52.線程池異常怎么處理知道嗎?

53.線程池有幾種狀態嗎?


1.Java中線程的創建方式?

1)繼承Thread類,重寫run()方法

class MyThread extends Thread {@Overridepublic void run() {System.out.println("繼承Thread,重寫run方法創建線程");}
}public class Main {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();}
}

2)實現Runnable接口,重寫run方法

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("實現Runnable接口,重寫run方法");}
}public class Main {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}
}

3)通過匿名內部類或者lambda表達式的方式實現,本質上還是前面兩種

4)實現Callable接口,重寫call方法(call方法可以理解為線程需要執行的任務),并且帶有返回值,這個返回表示一個計算結果,如果無法計算結果,則引發Exception異常

class MyCallableTest implements Callable<Integer> {@Overridepublic Integer call() throws Exception {System.out.println("創建線程:" + Thread.currentThread().getName());return 2;}
}public class Main {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<Integer> task = new FutureTask<>(new MyCallableTest());Thread thread = new Thread(task);thread.start();System.out.println("創建線程的返回結果為:" + task.get());}}

5)使用線程池創建線程,使用submit方法,把任務提交到線程池中即可,線程池中會有線程來完成這些任務

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Pool {public static void main(String[] args) {ExecutorService pool = Executors.newCachedThreadPool();pool.submit(new Runnable() {@Overridepublic void run() {//執行業務邏輯for(int i = 1; i <= 100; i++) {System.out.println("線程:" + Thread.currentThread().getName() + "執行了任務" + i + "~");}}});pool.submit(new Runnable() {@Overridepublic void run() {//執行業務邏輯for(int i = 101; i <= 200; i++) {System.out.println("線程:" + Thread.currentThread().getName() + "執行了任務" + i + "~");}}});pool.submit(new Runnable() {@Overridepublic void run() {//執行業務邏輯for(int i = 201; i <= 300; i++) {System.out.println("線程:" + Thread.currentThread().getName() + "執行了任務" + i + "~");}}});}
}

2.什么是線程池,有哪幾種常用的線程池?

線程池:事先創建若干個可執行的線程放入一個池(容器) 中, 需要的時候從池中獲取線程不用自行創建, 使用完畢不需要銷毀線程而是放回池中, 從而減少創建和銷毀線程對象的開銷。

Java中四種常用的線程池由java.util.concurrent包中的工具類Executors提供,它們是通過不同的參數配置創建出不同特性的ThreadPoolExecutor實例,四種常用的線程池:

1)Executors.newFixedThreadPool(int n):有固定數量線程的線程池。

  • 特點:線程池的大小是固定的,且在核心線程數和最大線程數相等的情況下,所有線程都為非守護線程。
  • 使用場景:適用于處理大量短生命周期的任務,能有效控制并發線程的數量,防止過多線程消耗系統資源。
  • 阻塞隊列通常使用無界隊列(如LinkedBlockingQueue),當線程池滿載時,新提交的任務會被放入隊列等待執行。

2)Executors.newCacheThreadPool():可緩存線程池。

  • 特點:線程池的大小是可變的,并且可以無限增長,直到系統資源耗盡。當一個任務完成時,該線程會返回到線程池中進行復用,如果線程池中的線程數量超過核心線程數并且有空閑線程,則會回收一些空閑線程。
  • 使用場景:適合處理大量臨時、快速的任務,例如網絡請求或IO密集型操作。
  • 阻塞隊列通常使用SynchronousQueue,這意味著每個任務都會直接分配給線程,如果沒有可用線程,則會嘗試創建新的線程來執行任務。

3)?Executors.newScheduledThreadPool(int n):支持定時與周期性任務執行的線程池。

  • 特點:這是一個定長線程池(線程池中核心線程的數量是固定的),主要用于執行定時及周期性任務。
  • 使用場景:當你需要按計劃調度任務執行時,比如定期執行清理工作、統計報告生成等。
  • 可以指定核心線程數,同時提供了schedule()和scheduleAtFixedRate()等方法來安排任務在固定延遲后執行,或者按照固定速率執行。

4)?Executors.newSingleThreadExecutor():

  • 特點:線程池只有一個工作線程,因此所有的任務都是串行執行的。
  • 使用場景:適用于需要保證順序執行任務的場景,或者系統資源有限不想額外開銷更多線程的情況。
  • 同樣,由于只有一個工作線程,所以阻塞隊列的作用在于存儲待執行的任務。

3.線程的生命周期?

線程的生命周期包括:新建、就緒、運行、阻塞、死亡。

新建:當線程對象創建后即進入了新建狀態(如:Thread th= new MyThread();)

就緒:當調用線程對象的start()方法(t.start();),線程即進入就緒狀態。處于就緒狀態的線程,只是說明此線程已經做好了準備,隨時等待CPU調度執行,并不是說執行了t.start()此線程立即就會執行;

運行:當CPU開始調度處于就緒狀態的線程時,此時線程才得以真正執行,即進入到運行狀態。(注:就緒狀態是進入到運行狀態的唯一入口,也就是說,線程要想進入運行狀態執行,首先必須處于就緒狀態中;)

阻塞:處于運行狀態中的線程由于某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才有機會再次被CPU調用以進入到運行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

1.等待阻塞:運行狀態中的線程執行wait()方法,使本線程進入到等待阻塞狀態;

2.同步阻塞:線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態;

3.其他阻塞:通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。

死亡:線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

4.終止線程的四種方式?

1.程序運行結束,線程自動結束。

2.使用退出標志退出線程,定義了一個退出標志 exit,在run()方法中,當 exit 為 true 時,while 循環退出,exit 的默認值為 false。在定義 exit 時,使用了一個 Java 關鍵字 volatile,這個關鍵字的目的是使 exit 同步,也就是說在同一時刻只能由一個線程來修改 exit 的值。

3.Interrupt 方法結束線程。

4.使用stop方法。

5.sleep()與wait()方法的區別?

1、這兩個方法來自不同的類分別是Thread和Object,sleep方法屬于Thread類中的靜態方法,wait屬于Object的成員方法。
2、sleep()是線程類(Thread)的方法,不涉及線程通信,調用時會暫停此線程指定的時間,但監控依然保持,不會釋放對象鎖,到時間自動恢復;wait()是Object的方法,用于線程間的通信,調用時會放棄對象鎖,進入等待隊列,待調用notify()/notifyAll()喚醒指定的線程或者所有線程,才進入對象鎖定池準備獲得對象鎖進入運行狀態。
3、wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用(使用范圍)。
4、sleep()方法必須捕獲異常InterruptedException,而wait()\notify()以及notifyAll()不需要捕獲異常。

6.start()與run()的區別?

創建線程:創建態,調用start()方法:就緒態,獲取cpu調度:執行run()方法運行態。

1、start方法用來啟動相應的線程;

2、run方法只是thread的一個普通方法,在主線程里執行;

3、需要并行處理的代碼放在run方法中,start方法啟動線程后自動調用run方法;

4、run方法必須是public的訪問權限,返回類型為void。

7.什么是線程安全?java中如何保證線程安全?Vector是一個線程安全類嗎?

線程安全是多線程編程中的一種概念,它涉及到如何在多個線程同時訪問資源時確保資源的正確使用和保護。線程安全意味著一個類或方法在多線程環境中可以安全地被多個線程同時訪問,而不會導致數據的不一致或者其他不可預知的結果。

在Java中保障線程安全有多種方式。以下是其中幾種常見的方式:

  1. 使用同步方法或同步代碼塊:通過在方法聲明中添加synchronized關鍵字或在代碼塊中使用synchronized關鍵字來確保在同一時間只有一個線程可以訪問方法或代碼塊。這樣可以防止多個線程同時訪問共享資源。

  2. 使用ReentrantLock類:ReentrantLock類是Java提供的一個可重入鎖類,可以通過調用其lock()方法獲取鎖,并在操作完共享資源后調用unlock()方法釋放鎖。這樣可以確保只有一個線程可以獲取到鎖,并執行相關操作。

  3. 使用volatile關鍵字:在多線程環境下,volatile關鍵字可以確保每次讀取變量時都從主內存中讀取,并且每次修改變量時都立即寫入主內存。這樣可以避免線程之間的數據不一致問題。

  4. 使用Atomic類:Atomic類是Java提供的一組原子操作類,可以保證對可變變量的讀取和修改操作具有原子性。這樣可以確保多個線程同時訪問同一個變量時不會發生數據競爭。

  5. 使用線程安全的數據結構:Java提供了一些線程安全的數據結構,如ConcurrentHashMap、ConcurrentLinkedQueue等,它們內部實現了線程安全的操作,可以在多線程環境下安全地使用。

  6. 使用ThreadLocal類:ThreadLocal類可以為每個線程提供獨立的變量副本,確保每個線程都可以訪問自己的變量副本,避免了線程間的數據競爭。

Vector 是線程安全類。Vector 的實現方式是使用同步鎖 synchronized 來保證線程安全。因此,在多線程環境下,多個線程可以同時訪問 Vector 中的元素,而不會出現數據錯誤的問題。不過,由于使用 synchronized 會帶來一定的性能損耗,因此在單線程環境下,使用 ArrayList 比使用 Vector 更容易獲得更好的性能表現。

8.什么是volatile關鍵字?它有什么作用?它的實現原理?

volatile關鍵字修飾的變量可以保證可見性與有序性,無法保證原子性。

當一個變量被定義成volatile之后,它將具備兩項特性:第一項是保證此變量對所有線程的可見性,這里的“可見性”是指當一條線程修改了這個變量的值,新值對于其他線程來說是可以立即得知的。

使用volatile變量的第二個語義是禁止指令重排序優化,保證有序性,普通的變量僅會保證在該方法的執行過程中所有依賴賦值結果的地方都能獲取到正確的結果,而不能保證變量賦值操作的順序與程序代碼中的執行順序一致。

原理:

  • 將線程工作內存中的值寫回主內存中
  • 通過緩存一致性協議,令其他線程工作內存中的該共享變量值失效
  • 其他線程會重新從主內存中獲取最新的值

重排序是指編譯器和處理器為了優化程序性能而對指令序列進行重新排序的一種手段。java語言規范規定JVM線程內部維持順序化語義。即只要程序的最終結果與它順序化情況的結果相等,那么指令的執行順序可以與代碼順序不一致,此過程叫指令的重排序。指令重排序的意義是什么?JVM能根據處理器特性(CPU多級緩存系統、多核處理器等)適當的對機器指令進行重排序,使機器指令能更符合CPU的執行特性,最大限度的發揮機器性能。

內存屏障(Memory Barrier)又稱內存柵欄,是一個CPU指令,它的作用有兩個,一是保證特定操作的執行順序,二是保證某些變量的內存可見性(利用該特性實現volatile的內存可見性)。由于編譯器和處理器都能執行指令重排優化。如果在指令間插入一條Memory Barrier則會告訴編譯器和CPU,不管什么指令都不能和這條Memory Barrier指令重排序,也就是說通過插入內存屏障禁止在內存屏障前后的指令執行重排序優化。Memory Barrier的另外一個作用是強制刷出各種CPU的緩存數據,因此任何CPU上的線程都能讀取到這些數據的最新版本。總之,volatile變量正是通過內存屏障(lock指令)實現其在內存中的語義,即可見性和禁止重排優化。

9.什么是死鎖?如何避免死鎖?

當兩個或多個線程同時持有自己的鎖,并且等待對方釋放鎖,就會形成死鎖。簡單來說,死鎖就是兩個或多個線程無限期地阻塞等待對方持有的鎖。

避免死鎖最簡單的方法是破壞死鎖的4個必要條件之一。

死鎖的產生必須同時滿足以下四個必要條件:

  1. 互斥條件(Mutual exclusion):至少有一個資源被持有,且在任意時刻只有一個進程能夠使用該資源。

  2. 請求與保持條件(Hold and wait):進程已經持有至少一個資源,并且在等待獲取其他進程持有的資源。

  3. 不剝奪條件(Non-preemption):進程已經獲得的資源在未使用完之前不能被剝奪,只能自愿釋放。

  4. 循環等待條件(Circular wait):進程之間形成一種頭尾相接的循環等待資源關系。

這四個條件缺一不可,同時滿足這四個條件才會發生死鎖。因此,避免死鎖必須從這四個條件入手,打破其中任意一個條件都能夠避免死鎖的發生。

具體地,在java中,為了避免死鎖,可以采取以下策略:

避免嵌套鎖:盡量避免在一個線程中同時持有多個鎖。如果必須這樣做,確保每次只鎖定一個資源,然后釋放它以獲取下一個資源。
鎖順序:確保所有線程都按照相同的順序獲取鎖。這樣可以避免循環等待條件,因為每個線程都知道下一個鎖的位置,從而避免了死鎖。
鎖超時:為鎖設置超時時間,以便在等待時間過長時放棄鎖定。這樣可以避免線程無限期地等待其他線程釋放資源。
鎖分段:將一個大的鎖分割成多個小的鎖,以減少多個線程同時爭奪同一個鎖的可能性。這樣可以降低死鎖的風險。
避免在持有鎖時進行I/O操作:I/O操作可能導致線程阻塞,這會增加死鎖的風險。盡量在持有鎖之前完成I/O操作,或者使用異步I/O來避免阻塞。
使用java.util.concurrent包中的工具:Java提供了java.util.concurrent包中的工具類,如Lock、Semaphore和CountDownLatch等,可以幫助避免死鎖。這些工具類提供了更靈活的鎖定機制,可以更好地控制并發訪問資源的方式。

10.什么是CAS操作?底層原理?它有什么特點(優缺點)?

CAS(Compare and Swap)是一種樂觀鎖機制,它也被稱為無鎖機制。CAS算法的作用:解決多線程條件下使用悲觀鎖造成性能損耗問題的算法,CAS基于樂觀鎖思想來設計的,其不會引發阻塞,synchronized會導致阻塞。CAS保證原子性,這個原子操作是由CPU來完成的。
CAS原理:CAS算法有三個操作數,通過內存中的值(V)、預期原始值(A)、修改后新值(B)。
(1)如果內存中的值和預期原始值相等, 就將修改后的新值保存到內存中。
(2)如果內存中的值和預期原始值不相等,說明共享數據已經被修改,放棄已經所做的操作,然后重新執行剛才的操作,直到重試成功。
注意:
(1)預期原始值(A)是從偏移位置讀取到三級緩存中讓CPU處理的值,修改后的新值是預期原始值經CPU處理暫時存儲在CPU的三級緩存中的值,而內存指定偏移位置中的原始值。
(2)比較從指定偏移位置讀取到緩存的值與指定內存偏移位置的值是否相等,如果相等則修改指定內存偏移位置的值,這個操作是操作系統底層匯編的一個原子指令實現的,保證了原子性。

AtomicInteger等原子類沒有使用synchronized鎖,而是通過volatile和CAS(Compare And Swap)解決資源的線程安全問題。
(1)volatile保證了可見性和有序性。
(2)CAS保證了原子性,而且是無鎖操作,提高了并發效率。

CAS機制實現的鎖是自旋鎖,如果線程一直無法獲取到鎖,則一直自旋,不會阻塞

CAS線程不會阻塞,線程一致自旋。
syncronized會阻塞線程,會進行線程的上下文切換,會由用戶態切換到內核態,切換前需要保存用戶態的上下文,而內核態恢復到用戶態,又需要恢復保存的上下文,非常消耗資源。

1)ABA問題
如果一個線程t1正修改共享變量的值A,但還沒修改,此時另一個線程t2獲取到CPU時間片,將共享變量的值A修改為B,然后又修改為A,此時線程t1檢查發現共享變量的值沒有發生變化,但是實際上卻變化了。
解決辦法: 使用版本號,在變量前面追加上版本號,每次變量更新的時候把版本號加1,那么A-B-A 就會變成1A-2B-3A。從Java1.5開始JUC包里提供了一個類AtomicStampedReference來解決ABA問題。AtomicStampedReference類的compareAndSet方法作用是首先檢查當前引用是否等于預期引用,并且當前版本號是否等于預期版本號,如果全部相等,則以原子方式將該引用和該標志的值設置為給定的更新值。

(2)循環時間長開銷會比較大:自旋重試時間,會給CPU帶來非常大的執行開銷

(3)只能保證一個共享變量的原子操作,不能保證同時對多個變量的原子性操作

CAS只能保證變量的原子性,不能保證變量的內存可見性。CAS獲取共享變量的值時,需要和volatile配合使用,來保證共享變量的可見性

11.什么是線程間通信?如何實現線程間通信?

線程通信指的是多個線程之間通過共享內存或消息傳遞等方式來協調和同步它們的執行。在多線程編程中,通常會出現多個線程需要共同完成某個任務的情況,這時就需要線程之間進行通訊,以保證任務能夠順利地執行。

Java中線程通訊的實現方法有以下幾種:

  1. 等待和通知機制:使用 Object 類的 wait() 和 notify() 方法來實現線程之間的通訊。當一個線程需要等待另一個線程執行完某個操作時,它可以調用 wait() 方法使自己進入等待狀態,同時釋放占有的鎖,等待其他線程調用 notify() 或 notifyAll() 方法來喚醒它。被喚醒的線程會重新嘗試獲取鎖并繼續執行。

  2. 信號量機制:使用 Java 中的 Semaphore 類來實現線程之間的同步和互斥。Semaphore 是一個計數器,用來控制同時訪問某個資源的線程數。當某個線程需要訪問共享資源時,它必須先從 Semaphore 中獲取一個許可證,如果已經沒有許可證可用,線程就會被阻塞,直到其他線程釋放了許可證。

  3. 柵欄機制:使用 Java 中的 CyclicBarrier 類來實現多個線程之間的同步,它允許多個線程在指定的屏障處等待,并在所有線程都達到屏障時繼續執行。

  4. 鎖機制:使用 Java 中的 Lock 接口和 Condition 接口來實現線程之間的同步和互斥。Lock 是一種更高級的互斥機制,它允許多個條件變量(Condition)并支持在同一個鎖上等待和喚醒。

12.線程、進程、協程、程序的區別?

程序其實就是存在操作系統上的一大堆指令(指令序列),是一個靜態的概念。

進程是操作系統分配資源(比如內存)和調度的基本單位,是一個動態的概念。

線程是運行(執行)的基本單位,是輕量級的進程,但是和進程不同的是,線程它沒有自主獨立的操作空間和資源,因為一個進程往往有很多個線程并發進行,因此它們一般是共享自己本進程的資源和內存的。

一個線程中可以有任意多個協程,但某一時刻只能有一個協程在運行,多個協程分享該線程分配到的計算機資源。協程的本質其實也是一個線程。

13.什么是原子操作?Java中如何保證原子操作?

原子操作是指在執行過程中不會被其他線程中斷的操作。它要么全部執行成功,要么全部不執行,不存在中間狀態。原子操作可以保證數據的一致性和線程安全性。

在Java中,提供了一些原子操作的類,如AtomicInteger、AtomicLong、AtomicBoolean等。這些類提供了一些常見的原子操作方法,如get、set、incrementAndGet、compareAndSet等。

通過使用原子操作類,我們可以在多線程環境下安全地對共享變量進行操作,而不需要使用鎖機制或同步代碼塊。

14.什么是線程的上下文切換?如何減少上下文切換的開銷?

線程上下文切換是指CPU從一個線程中斷,轉而執行另一個線程的過程。

線程上下文切換一般會發生在如下場景:

1、當一個線程的時間片用完,操作系統會將其掛起,轉而執行其他可運行的線程,從而發生上下文切換。

2、等待事件:當一個線程需要等待某個事件發生時,它會進入阻塞狀態,此時操作系統會將該線程的上下文保存在內存中,并切換到其他線程。當等待的事件發生后,操作系統會恢復之前被阻塞的線程的上下文,并將其重新調度執行。

3、線程同步:當多個線程需要同時訪問共享資源時,由于緩存一致性協議的需要,此時會發生線程上下文的切換。

4、線程被中斷:當一個線程被強制終止時,操作系統會回收其上下文信息,并切換到其他正在運行的線程的執行現場。

線程上下文切換是一種比較消耗性能的操作,因為它需要保存和恢復大量的上下文信息。因此,減少線程上下文切換的次數可以提高系統的性能。

無鎖并發編程:就是多線程競爭鎖時,會引起上下文切換,多線程處理數據時,可以用一 些辦法來避免使用鎖,如將數據的ID按照Hash算法取模分段,不同的線程處理不同段的數據。

CAS算法:Java的Atomic包使用CAS算法來更新數據,就是它在沒有鎖的狀態下,可以保證多個線程對一個值的更新。

使用最少線程:避免創建不需要的線程。

協程:在單線程里實現多任務的調度,并在單線程里維持多個任務間的切換。

15.什么是線程安全的集合?Java中有哪些線程安全的集合類?

線程安全集合是指該集合可以在多線程并發讀取的狀態下,能夠保持數據集合有序,不發生同步錯誤。

VectorArrayList類似,是長度可變的數組,與ArrayList不同的是,Vector是線程安全的,它幾乎給所有的public方法都加上了sychronized關鍵字。由于加鎖倒是性能降低,在不需要并發訪問時,這種強制性的同步就顯得多余,所以現在幾乎沒有什么人在使用。

java.util.concurrent包中的集合:ConcurrentHashMapHashTable

HashTable的加鎖方法是給每個方法加上synchronized關鍵字,這樣鎖的是整個對象。而ConcurrentHashMap是有更細粒度的鎖。在JDK1.8之前,ConcurrentHashMap加的是分段鎖,即Segment,每個Segment含有整個table的一部分,這樣不同分段之間的并發操作就互不影響。
JDK1.8之后對此進行了進一步的改進,取消了Segment,直接在table元素上加鎖,實現對每一行加鎖,進一步減小了并發沖突的概率。

16.說一下JMM(Java內存模型)?再介紹一下JVM的內存模型?

Java內存模型(Java Memory Model簡稱JMM) 是一種抽象的概念,并不真實存在,它描述的是一組規則或規范。通過這組規則控制程序中各個變量在共享數據區域和私有數據區域的訪問方式,通過這組規范定義了程序中各個變量(包括實例字段,靜態字段和構成數組對象的元素)的訪問方式。

本質上JMM是為了在多線程下保證解決?原子性,可見性,有序性問題。

JVM內存模型:一般指運行時數據區,包括5個部分,程序計數器、虛擬機棧、本地方法棧、堆、方法區。

程序計數器是一個記錄著當前線程所執行的字節碼的行號指示器。

Java虛擬機棧(Java Virtual Machine Stacks)是每個線程運行時所需的內存。每個棧由多個棧幀(Stack Frame)組成,每個方法執行都會創建一個棧幀,對應著該方法調用時所占用的內存,棧幀包含局部變量表、操作數棧、動態連接、方法出口等。

本地方法棧(Native Method Stack)與虛擬機棧作用大致相同。區別是虛擬機棧為虛擬機執行Java方法服務,本地方法棧為虛擬機使用到的Native 方法服務。? ?

是 Java 虛擬機管理的內存中最大的一塊,被所有線程共享。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例以及數據都在這里分配內存。

為了進行高效的垃圾回收,虛擬機把堆內存邏輯上劃分成三塊區域(分代的唯一理由就是優化 GC 性能):

  • 新生帶(年輕代):新對象和沒達到一定年齡的對象都在新生代
  • 老年代(養老區):被長時間使用的對象,老年代的內存空間應該要比年輕代更大
  • 元空間(JDK1.8 之前叫永久代):像一些方法中的操作臨時對象等,JDK1.8 之前是占用 JVM 內存,JDK1.8 之后直接使用物理內存(非堆)

方法區用于存儲已被已經被虛擬機加載的類型信息、常量、靜態變量、即時編譯器編譯后的代碼等等。

17.如何創建線程池,線程池中 sumbit() 和 execute() 方法有什么區別?線程池的7大參數?

Java中四種常用的線程池由java.util.concurrent包中的工具類Executors提供,它們是通過不同的參數配置創建出不同特性的ThreadPoolExecutor實例。

?區別:

1.返回值

`submit()` 方法可以接受 `Callable` 或 `Runnable` 類型的任務,并返回一個 `Future` 對象,你可以通過 `Future` 對象獲取任務的執行結果或者取消任務執行。

? ?`execute()` 方法只接受 `Runnable` 類型的任務,并且沒有返回值。

2.異常處理

??`submit()` 方法可以捕獲任務執行過程中拋出的異常,你可以通過 `Future` 對象來獲取任務執行過程中拋出的異常。

???`execute()` 方法無法直接捕獲任務執行中的異常,需要在任務內部進行異常處理,否則可能導致線程池中的線程被異常終止。

3.方法來源

?`execute()` 方法是 `Executor` 接口中定義的方法,較為簡單,用于執行 `Runnable` 任務。

? ?`submit()` 方法是 `ExecutorService` 接口中定義的方法,在 `Executor` 的基礎上增加了任務提交后可以獲取任務執行結果的能力。

線程池的7大參數:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler){}

?1.核心線程數:線程池中會維護一個最小的線程數量,即使這些線程處理空閑狀態,他們也不會被銷毀,除非設置了allowCoreThreadTimeOut。這里的最小線程數量即是corePoolSize。任務提交到線程池后,首先會檢查當前線程數是否達到了corePoolSize,如果沒有達到的話,則會創建一個新線程來處理這個任務。

2.最大線程數:當前線程數達到corePoolSize后,如果繼續有任務被提交到線程池,會將任務緩存到工作隊列中。如果隊列也已滿,則會去創建一個新線程來出來這個處理。線程池不會無限制的去創建新線程,它會有一個最大線程數量的限制,這個數量即由maximunPoolSize指定。

3.空閑線程存活時間:一個線程如果處于空閑狀態,并且當前的線程數量大于corePoolSize,那么在指定時間后,這個空閑線程會被銷毀,這里的指定時間由keepAliveTime來設定。

4.空閑線程存活時間單位:空閑線程存活時間的單位。

5.workQueue 工作隊列:

新任務被提交后,會先進入到此工作隊列中,任務調度時再從隊列中取出任務。jdk中提供了四種工作隊列:

①ArrayBlockingQueue

基于數組的有界阻塞隊列,按FIFO排序。新任務進來后,會放到該隊列的隊尾,有界的數組可以防止資源耗盡問題。當線程池中線程數量達到corePoolSize后,再有新任務進來,則會將任務放入該隊列的隊尾,等待被調度。如果隊列已經是滿的,則創建一個新線程,如果線程數量已經達到maxPoolSize,則會執行拒絕策略。

②LinkedBlockingQuene

基于鏈表的無界阻塞隊列(其實最大容量為Interger.MAX),按照FIFO排序。由于該隊列的近似無界性,當線程池中線程數量達到corePoolSize后,再有新任務進來,會一直存入該隊列,而基本不會去創建新線程直到maxPoolSize(很難達到Interger.MAX這個數),因此使用該工作隊列時,參數maxPoolSize其實是不起作用的。

③SynchronousQuene

一個不緩存任務的阻塞隊列,生產者放入一個任務必須等到消費者取出這個任務。也就是說新任務進來時,不會緩存,而是直接被調度執行該任務,如果沒有可用線程,則創建新線程,如果線程數量達到maxPoolSize,則執行拒絕策略。

④PriorityBlockingQueue

具有優先級的無界阻塞隊列,優先級通過參數Comparator實現。

6.threadFactory 線程工廠

創建一個新線程時使用的工廠,可以用來設定線程名、是否為daemon線程等等。

7.handler 拒絕策略

當工作隊列中的任務已到達最大限制,并且線程池中的線程數量也達到最大限制,這時如果有新任務提交進來,該如何處理呢。這里的拒絕策略,就是解決這個問題的,jdk中提供了4中拒絕策略:

①CallerRunsPolicy

該策略下,在調用者線程中直接執行被拒絕任務的run方法,除非線程池已經shutdown,則直接拋棄任務。

②AbortPolicy

該策略下,直接丟棄任務,并拋出RejectedExecutionException異常。

③DiscardPolicy

該策略下,直接丟棄任務,什么都不做。

④DiscardOldestPolicy

該策略下,拋棄進入隊列最早的那個任務,然后嘗試把這次拒絕的任務放入隊列

18.為什么使用線程池,線程池的底層原理?

線程池是運用場景最多的并發框架,幾乎所有需要一步或者并發執行任務的程序都可以使用線程池。使用線程池一般有以下三個好處:

①降低資源的消耗,通過重復利用已經創建的線程降低線程創建和銷毀造成的消耗。

②提高相應速度,當任務到達的時候,任務可以不需要等到線程創建就能立刻執行。

③提高線程的可管理性,線程是稀缺資源,使用線程池可以統一的分配、調優和監控。

線程池的底層原理:

待執行的任務提交后由分配的核心線程進行執行,當待執行的任務數目大于核心線程數,會將任務放到任務隊列,當提交的任務裝滿了任務隊列,會通過創建新的線程繼續處理,當創建的線程數目達到最大線程數,就采用相應的拒絕策略。

19.線程池的四種拒絕策略?

當工作隊列中的任務已到達最大限制,并且線程池中的線程數量也達到最大限制,這時如果有新任務提交進來,該如何處理呢。這里的拒絕策略,就是解決這個問題的,jdk中提供了4種拒絕策略:

①CallerRunsPolicy

該策略下,在調用者線程中直接執行被拒絕任務的run方法,除非線程池已經shutdown,則直接拋棄任務。

②AbortPolicy

該策略下,直接丟棄任務,并拋出RejectedExecutionException異常。

③DiscardPolicy

該策略下,直接丟棄任務,什么都不做。

④DiscardOldestPolicy

該策略下,拋棄進入隊列最早的那個任務,然后嘗試把這次拒絕的任務放入隊列。

20.什么是 AQS 嗎?了解 AQS 共享資源的方式嗎?

AQS?( Abstract Queued Synchronizer )是一個抽象的隊列同步器,通過維護一個共享資源狀態( Volatile Int State )和一個先進先出( FIFO )的線程等待隊列來實現一個多線程訪問共享資源的同步框架。

??AQS 定義了兩種資源共享方式 :獨占式 (Exclusive)和共享式(Share)

  • 獨占式:只有一個線程能執行,具體的 Java 實現有 ReentrantLock。
  • 共享式:多個線程可同時執行,具體的 Java 實現有 Semaphore和CountDownLatch。

?AQS只是一個框架 ,只定義了一個接口,具體資源的獲取、釋放都 由自定義同步器去實現。不同的自定義同步器爭用共享資源的方式也不同,自定義同步器在實現時只需實現共享資源state的獲取與釋放方式即可,至于具體線程等待隊列的維護,如獲取資源失敗入隊、喚醒出隊等, AQS 已經在頂層實現好,不需要具體的同步器再做處理。
?

21.sychronized的底層原理?鎖升級過程?

synchronized是JDK自帶的一個關鍵字,用于在多線程的情況下,保證線程安全;在JDK1.5之前是一個重量級鎖,1.6之后進行了優化,性能有很大提升。synchronized可以用來同步方法同步代碼塊同步靜態方法。

Synchronized底層通過?個monitor的對象來完成,每個對象有?個監視器鎖(monitor)。當monitor被占?時就會處于鎖定狀態,線程執?monitorenter指令時嘗 試獲取monitor的所有權,過程如下:

(1)如果monitor的進?數為0,則該線程進?monitor,然后將進?數設置為1,該線程即為monitor的所有者。
(2)如果線程已經占有該monitor,只是重新進?,則進?monitor的進?數加1。
(3)如果其他線程已經占?了monitor,則該線程進?阻塞狀態,直到monitor的進?數為0,再重新嘗試獲取monitor的所有權。
執?monitorexit的線程必須是object所對應的monitor的所有者。指令執?時,monitor的進?數減1,如果減1 后進?數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去獲取這個monitor的所有權。這樣也就不難理解Synchronized是可重入鎖了。

鎖也分不同狀態,JDK6之前只有兩個狀態:無鎖、有鎖(重量級鎖),而在JDK6之后對synchronized進行了優化,新增了兩種狀態,總共就是四個狀態:無鎖狀態、偏向鎖、輕量級鎖、重量級鎖,其中無鎖就是一種狀態了。

無鎖:無鎖沒有對資源進行鎖定,所有的線程都能訪問并修改同一個資源,但同時只有一個線程能修改成功。

偏向鎖:是指一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖,降低獲取鎖的代價。

輕量級瑣(自旋鎖):輕量級鎖由偏向鎖升級而來,偏向鎖運行在一個線程同步塊時,第二個線程加入鎖競爭的時候,偏向鎖就會升級為輕量級鎖。

重量級瑣:自旋失敗,很大概率再一次自旋也是失敗,因此直接升級成重量級鎖,進行線程阻塞,減少cpu消耗,當鎖升級為重量級鎖后,未搶到鎖的線程都會被阻塞,進入阻塞隊列。

22.說說 AtomicInteger 和 synchronized 的異同點?

相同點:都是通過瑣的機制解決多線程資源共享存在的安全問題

不同點:AtomicInteger是基于volatile和cas,屬于樂觀鎖,volatile保證有序性與可見性,cas保證原子性,通過不斷自旋的方式獲瑣,不釋放cpu;synchronized關鍵字通過悲觀鎖的形式保證線程安全,有個瑣升級的過程。

23.原子類和 volatile 有什么異同?

volatile用于保證可見性和有序性,可見性:通過內存屏障的方式實現,線程對共享變量的修改會強制寫入主存。有序性:禁止指令重排序。volatile不保證原子性。

原子類用于保證多線程共享變量的原子性。

24.什么是CountDownLatch、CyclicBarrier、Semaphore?

在Java并發編程中,CountDownLatch、CyclicBarrier 和 Semaphore 是三種常用的同步工具類,它們都是 `java.util.concurrent` 包的一部分,用于多線程環境中的線程同步和協作。

1. CountDownLatch
? ?- CountDownLatch 主要用來實現一個或多個線程等待其他線程完成一組操作后才能繼續執行。它通過計數器來控制,構造時設置一個初始的計數值。
? ?- 當某個線程完成自己的任務后,調用 `countDown()` 方法使計數器減一。
? ?- 其他線程則可以調用 `await()` 方法進行等待,直到計數器歸零(即所有線程都調用了 `countDown()`)。一旦計數器為0,`await()` 將返回,允許這些線程繼續執行后續的任務。

2. CyclicBarrier
? ?- CyclicBarrier 也用于讓一組線程等待至某個點(或者說屏障),但與 CountDownLatch 不同的是,它可以被重用,因此得名“循環柵欄”。
? ?- 在初始化 CyclicBarrier 時指定一個參與線程的數量,當每個線程到達柵欄位置時調用 `await()` 方法,該方法會阻塞當前線程,直到所有線程都到達柵欄。
? ?- 所有線程都到達柵欄之后,CyclicBarrier 可以觸發一個回調(通過 `CyclicBarrier` 構造函數傳入 `Runnable`)或者僅僅釋放所有等待的線程繼續執行。
? ?- 當所有線程都到達并越過屏障后,CyclicBarrier 的計數器會自動重置,以便下一輪的使用。

3. Semaphore
? ?- Semaphore(信號量)是一種更為通用的同步工具,它維護了一個許可證池,用于控制同時訪問特定資源的線程數量。
? ?- 當一個線程想要訪問受保護的資源時,需要首先獲取一個許可證(通過調用 `acquire()` 或 `tryAcquire()` 方法),如果此時許可證可用,則線程會獲得許可并進入臨界區;若無可用許可證,則線程會被阻塞直到其他線程釋放許可證。
? ?- 線程完成對共享資源的訪問后,應調用 `release()` 方法釋放許可證,使得其他等待的線程有機會獲取許可證并執行相關操作。

這三個工具類均提供了靈活的方式來協調多線程間的同步和通信,根據實際場景選擇合適的一個來優化程序的性能和正確性。

25.CountDownLatch、CyclicBarrier、Semaphore的區別?

它們三個都是用于實現線程同步的工具類。

CountDownLatch通過減數計數器的方式協調線程同步,當前線程執行完每個任務后,計數器減1,當計數器為0時允許其它線程執行任務。

CyclicBarrier要求所有線程必須到達柵欄位置才允許繼續執行。

Semaphore是通過信號量的方式實現線程同步的,通過維護許可池的方式,每個線程想要進入臨界區訪問資源時,需要嘗試獲取許可,被允許后則可以訪問資源。

26.synchronized 和 lock 有什么區別?synchronized 和 Lock 如何選擇?Lock接口的主要方法?

區別:

1.synchronized是關鍵字,Lock是接口;

2.synchronized是隱式的加鎖,lock是顯式的加鎖;

3.synchronized可以作用于方法上,lock只能作用于代碼塊;

4.synchronized底層采用的是objectMonitor,lock采用的AQS;

5.synchronized是阻塞式加鎖,lock是非阻塞式加鎖支持可中斷式加鎖,支持超時時間的加鎖;

6.synchronized在進行加鎖解鎖時,只有一個同步隊列和一個等待隊列, lock有一個同步隊列,可以有多個等待隊列;

7.synchronized只支持非公平鎖,lock支持非公平鎖和公平鎖;

8.synchronized使用了object類的wait和notify進行等待和喚醒, lock使用了condition接口進行等待和喚醒(await和signal);

9.lock支持個性化定制, 使用了模板方法模式,可以自行實現lock方法;

兩種瑣的選擇:

從性能上來說,如果競爭資源不激烈,兩者的性能差不多,而競爭資源非常激烈時,此時lock的性能要遠遠優于synchronized,所以,在具體使用時候,要根據適當情況進行選擇。

lock接口的主要方法包括:

lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()都是用來獲取鎖的。unLock()方法是用來釋放鎖的。

lock():如果采用Lock,必須主動去釋放鎖,并且在發生異常時,不會自動釋放鎖。

tryLock():嘗試獲取瑣成功返回true,否則返回false,可以設置獲取瑣的時間。

lockInterruptibly()方法比較特殊,當通過這個方法去獲取鎖時,如果線程 正在等待獲取鎖,則這個線程能夠 響應中斷,即中斷線程的等待狀態。例如,當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有在等待,那么對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。

27.tryLock、lock和lockInterruptibly的區別?

lock():如果采用Lock,必須主動去釋放鎖,并且在發生異常時,不會自動釋放鎖。

tryLock():嘗試獲取瑣成功返回true,否則返回false,可以設置獲取瑣的時間。

lockInterruptibly()方法比較特殊,當通過這個方法去獲取鎖時,如果線程 正在等待獲取鎖,則這個線程能夠 響應中斷,即中斷線程的等待狀態。例如,當兩個線程同時通過lock.lockInterruptibly()想獲取某個鎖時,假若此時線程A獲取到了鎖,而線程B只有在等待,那么對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。

28.什么是阻塞隊列?列舉幾個常見的阻塞隊列?什么是非阻塞隊列?

阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作支持阻塞的插入和移除方法。

  1)支持阻塞的插入方法:意思是當隊列滿時,隊列會阻塞插入元素的線程,直到隊列不滿。?

  2)支持阻塞的移除方法:意思是在隊列為空時,獲取元素的線程會等待隊列變為非空。

阻塞隊列常用于生產者和消費者的場景,生產者是向隊列里添加元素的線程,消費者是從隊列里取元素的線程。

7種常見的阻塞隊列如下:

ArrayBlockingQueue :一個由數組結構組成的有界阻塞隊列。

LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列。

PriorityBlockingQueue :一個支持優先級排序的無界阻塞隊列。

DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。

SynchronousQueue:一個不存儲元素的阻塞隊列。

LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。

LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

非阻塞隊列就是插入和移除不進行阻塞處理,在多線程處理生產者和消費者問題時候需要額外加入保證隊列為空,其它線程不允許移除,隊列為滿,其它線程不允許插入操作。

29.什么是ThreadLocal,它是線程安全的嗎?底層原理是什么?會存在內存泄露嗎?

ThreadLocal主要關注的是如何為每個線程提供獨立的數據副本,避免共享,在多線程共享變量的情況下保證線程安全。

ThreadLocal底層是通過ThreadLocalMap來實現的,每個Thread對象(注意不是ThreadLocal對
象)中都存在?個ThreadLocalMap,Map的key為ThreadLocal對象,Map的value為需要緩存的
值;

如果在線程池中使?ThreadLocal會造成內存泄漏,因為當ThreadLocal對象使?完之后,應該要
把設置的key,value,也就是Entry對象進?回收,但線程池中的線程不會回收,?線程對象是通過強引?指向ThreadLocalMap,ThreadLocalMap也是通過強引?指向Entry對象,線程不被回收,Entry對象也就不會被回收,從?出現內存泄漏,解決辦法是,在使?了ThreadLocal對象之后,? 動調?ThreadLocal的remove?法,?動清除Entry對象。

30.HashTable、HashMap、ConcurrentHashMap有什么區別?

HashTable、HashMap和ConcurrentHashMap是Java中用于存儲鍵值對數據結構的三個不同實現,它們之間在設計原理、線程安全性和性能等方面存在顯著差異。

1. HashTable
- 底層原理:HashTable基于散列表(哈希表)實現,它使用鏈地址法解決哈希沖突,即每個桶(bucket)內部采用鏈表存儲多個哈希值相同的元素。
- 線程安全性:HashTable是線程安全的,它在執行插入、刪除和查找等操作時都會進行同步處理,這意味著每次只有一個線程能夠訪問Hashtable。它通過在方法上加鎖(synchronized關鍵字修飾)來保證線程安全。
- 性能:由于它的所有操作都是同步的,因此在高并發環境下,可能會因為競爭導致性能下降,不適合在多線程環境中有大量讀寫操作的場景。

2. HashMap
- 底層原理:HashMap同樣基于散列表實現,但在JDK 1.8之前,其內部節點數組+鏈表/紅黑樹的方式管理數據;1.8之后,當鏈表長度超過閾值時會轉換為紅黑樹以優化查詢效率。哈希函數用于計算鍵的哈希碼,并確定元素在數組中的位置。
- 線程安全性:HashMap是非線程安全的,在多線程環境下不保證正確性,如果多個線程同時修改HashMap,可能導致數據不一致、死鎖等問題。
- 性能:HashMap在單線程環境中提供了很高的性能,因為它沒有內置的同步機制,因此不需要額外的線程同步開銷。

3. ConcurrentHashMap
- 底層原理:ConcurrentHashMap也是基于散列表實現,但它從JDK 1.7開始引入了分段鎖(Segment + HashEntry)的設計,將整個散列表分為多個segment,每個segment可以獨立地進行讀寫操作,從而實現更高的并發性能;而在JDK 1.8及以后版本,放棄了Segment的概念,改用了一種更細粒度的CAS和Synchronized相結合的方式來保證線程安全,底層的數據結構簡化為Node數組,依然在必要時轉為紅黑樹。
- 線程安全性:ConcurrentHashMap是線程安全的,但與HashTable全表鎖不同,它是分區或粒度化的鎖策略,這意味著在多線程環境下,不同的部分可以同時進行讀寫操作,提高了并發性能。
- 性能:相較于HashTable,ConcurrentHashMap犧牲了一定程度上的簡單性,換取了更好的并發性能,適用于多線程環境下的高并發讀寫操作。

總結:
- HashTable是最原始且線程安全的實現,但并發性能較差。
- HashMap提供了最高的非線程安全性能,適合單線程應用。
- ConcurrentHashMap在線程安全的前提下實現了較好的并發性能,是多線程環境下首選的散列表實現。

31、什么是樂觀瑣,什么是悲觀鎖,什么是公平鎖,什么是非公平瑣?

樂觀鎖(Optimistic Locking)
- 原理:樂觀鎖假設并發環境下數據沖突不頻繁,因此在讀取數據時并不立即加鎖,而是允許所有事務讀取和修改數據。當事務準備提交時,才會檢查在此期間是否有其他事務對數據進行了修改,通常通過版本號或CAS(Compare and Swap)機制來判斷。如果發現有沖突,則事務回滾并重新嘗試。

? ?例子:
? ?- 在數據庫中,樂觀鎖可以通過為表的某個字段添加一個版本號來實現。例如,在更新用戶余額時,首先讀取當前余額和版本號,然后計算新的余額,最后在更新語句中同時更新余額和版本號,并要求版本號不變。如果有其他事務在這段時間內更新了余額,那么版本號已經改變,該更新將失敗,需要重新讀取最新的數據再次嘗試。

悲觀鎖(Pessimistic Locking)
- 原理:悲觀鎖假設并發環境下數據沖突是常態,所以在訪問數據時就先鎖定資源,直到事務結束才釋放鎖,確保其他事務在該時間段內無法修改此數據。

? 例子:
? ?- 在數據庫中,悲觀鎖可以使用`SELECT ... FOR UPDATE`語句來獲取行級鎖。比如在轉賬操作中,事務A在執行扣款前會鎖定賬戶A的記錄,直到事務提交或者回滾后才會釋放這個鎖,這樣在同一時間,事務B就不能再鎖定同一賬戶進行扣款操作,避免了并發問題。

公平鎖(Fair Lock)
- 原理:公平鎖是一種線程調度策略,它保證了等待鎖最久的線程在鎖釋放時能夠獲得鎖,即按照線程請求鎖的順序依次分配,不存在“插隊”現象。

? ?例子:
? ?- 在Java中的ReentrantLock類中,可以通過構造函數指定是否啟用公平鎖。如果是一個公平鎖,那么當鎖被釋放時,它會優先選擇已經在等待隊列中等待最久的那個線程給它上鎖。

非公平鎖(Non-Fair Lock)
- 原理:非公平鎖則不保證等待時間最長的線程一定先獲得鎖,即使有的線程已經等待了很久,新到來的線程也有可能直接獲得鎖。

? ?例子:
? ?- Java中的synchronized關鍵字所實現的鎖是非公平鎖,默認情況下ReentrantLock也是非公平鎖。在這種情況下,一旦鎖被釋放,任何等待的線程都有可能獲得鎖,這可能導致某些線程長時間得不到鎖而饑餓。

32、java中常見的四種引用類型?

一、強引用

  強引用是最常見的,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。當一個對象被強引用變量引用時,只要強引用還在,處于可達狀態,垃圾回收器就永遠不會回收這個對象。即使系統內存不足,JVM也不會回收該對象來釋放內存,而是拋出OOM。

二、軟引用

  軟引用需要用SoftReference類來實現,對于只有軟引用的對象來說,當系統內存足夠時它不會被回收,當系統內存空間不足時它會被回收。當系統內存空間不足時它會被回收。軟引用通常用在對內存敏感的程序中。緩存數據,提高數據的獲取速度。

三、弱引用

  弱引用需要用WeakReference類來實現,它比軟引用生存期更短,對于只有弱引用的對象來說,只要垃圾回收機制一運行,不管JVM內存空間是否足夠,總會回收該對象占用的內存。短時間緩存某些次要數據。

四、虛引用

  虛引用需要PhantomReference類來實現,它不能單獨使用,必須和引用隊列聯合使用,虛引用的主要作用是跟蹤對象被垃圾回收的狀態。它的唯一目的是為了能在對象被收集器回收時收到一個系統通知,通過與引用隊列(ReferenceQueue)聯合使用,可以在對象被回收后執行特定的操作。

33、java線程中涉及到的常見方法及其意義?線程基本方法 、線程等待(wait) 、線程睡眠(sleep) 線程讓步(yield) 線程中斷(interrupt) 、Join等待其他線程終止 、為什么要用join()方法?

?

Java線程中涉及到的常見方法及其意義:

1. 線程基本方法
? ?- `start()`:啟動一個新線程并執行其`run()`方法。每個線程只能調用一次`start()`方法。
? ?- `run()`:線程要執行的任務的具體邏輯,通常用戶需要重寫Thread類或實現Runnable接口中的`run()`方法。
? ?- `currentThread()`:返回當前正在執行的線程對象。

2. 線程等待(wait)
? ?- `wait()`:在一個已經獲取到對象鎖的同步方法或同步塊中調用,會使當前線程釋放該對象鎖,并進入等待狀態直到被其他線程通過notify()或notifyAll()喚醒,或者超時后自動喚醒。
? ?- `notify()`:喚醒在此對象監視器上等待的一個單個線程,如果有多個線程在等待,則選擇其中一個喚醒。
? ?- `notifyAll()`:喚醒在此對象監視器上等待的所有線程。

3. 線程睡眠(sleep)
? ?- `Thread.sleep(long millis)`:使當前線程暫停指定毫秒數的時間,讓出CPU給其他線程。需要注意的是,即使時間到了,也不能保證立即恢復執行,因為這還取決于操作系統的線程調度策略。

4. 線程讓步(yield)
? ?- `yield()`:提示當前線程放棄處理器使用權,但不保證會讓出,只是一個建議行為,具體是否讓出由JVM決定。它主要用于減少程序中優先級較高的線程對低優先級線程的阻塞影響,提高并發性能。

5. 線程中斷(interrupt)
? ?- `interrupt()`:設置線程的中斷標志位,不直接終止線程,而是傳遞一個中斷請求信號。線程在運行過程中可以通過檢查自身的中斷狀態(`isInterrupted()`)來響應中斷請求,也可以通過捕獲`InterruptedException`異常來處理中斷。
? ?- `isInterrupted()`:判斷線程是否已被中斷,不會清除中斷狀態。
? ?- `interrupted()`:判斷當前線程是否被中斷并清除中斷狀態。

6.Join等待其他線程終止
? ?- `join()`:允許一個線程A等待另一個線程B完成執行,當在A線程上調用B線程的join()方法時,A會等待B線程結束后再繼續執行。這樣可以確保某個線程在其依賴的線程完成后才開始執行,有助于保持程序邏輯的一致性和正確性。

為什么要用join()方法?
- join()方法在多線程協作場景下非常重要,例如:
? - 當主線程需要等待子線程執行完畢后再進行下一步操作時,可以使用join()方法避免主線程提前結束導致子線程未完成任務的情況。
? - 在多線程間的邏輯依賴關系中,有時必須確保前一個線程先完成工作,后續線程才能基于它的結果執行,此時join()方法能夠幫助我們控制線程間的順序和依賴關系。
? - 用于測試多線程程序時,方便觀察線程執行順序和結果一致性。

34、并發編程的三要素?

并發編程的三要素是指在設計和實現多線程程序時需要特別關注的三個方面,以確保線程安全性和正確性。這三要素是:

1. 原子性(Atomicity)
? ?原子性指的是一個操作或者多個操作要么全部執行完成,要么都不執行。在多線程環境下,如果一個操作不是原子性的,那么可能會導致數據不一致的問題。例如,假設一個操作涉及對兩個或更多變量的修改,如果在沒有同步措施的情況下被中斷,可能導致部分變量已更改而其他變量未更改,從而破壞了數據完整性。在Java中,可以通過鎖(如`synchronized`關鍵字、Lock接口等)來保證某個代碼塊的原子性。

2. 可見性(Visibility)
可見性是指當一個線程修改了共享變量的值后,其他線程能夠立即看到這個新值。由于處理器緩存、編譯器優化等因素,如果沒有適當的同步機制,可能造成不同線程間對同一變量的不同版本視圖。Java提供了volatile關鍵字來解決這個問題,它能確保對volatile變量的寫操作會立即刷新到主內存,并且讀操作總是從主內存中獲取最新值。

3. 有序性(Ordering)
? ?有序性是指程序執行的順序按照代碼的邏輯順序進行。然而,在硬件層面,為了提高性能,處理器可能會重新排序指令執行順序(即指令重排序),這可能導致多線程環境下的不確定性。在Java中,通過內存模型和happens-before原則提供了一定程度上的有序性保障。synchronized和volatile關鍵字不僅保證了可見性,同時也一定程度上保證了有序性,禁止特定類型的重排序。

要編寫正確的并發程序,開發者必須理解并妥善處理這三個要素,以確保線程間的交互滿足預期的行為。

35、ReentrantLock與sychronized的區別?實現原理?使用場景?

ReentrantLock與synchronized的區別:

1. 獲取和釋放鎖的方式:
? ?- `synchronized`是Java的內置關鍵字,通過它修飾的方法或代碼塊自動獲取并釋放鎖。不需要手動管理鎖的生命周期。
? ?- `ReentrantLock`是Java并發庫(java.util.concurrent.locks)中的一個接口`Lock`的實現類,需要顯式調用`lock()`和`unlock()`方法來獲取和釋放鎖。

2. 公平性選擇:
? ?- `synchronized`默認是非公平鎖,即無法保證線程等待的順序,新到達的線程可能搶占已經等待的線程。
? ?- `ReentrantLock`提供了公平鎖和非公平鎖的選擇,通過構造函數指定`new ReentrantLock(true)`可以獲得公平鎖,這樣等待時間最長的線程將優先獲得鎖。

3. 響應中斷:
? ?- 使用`synchronized`時,如果一個線程在等待鎖的過程中被中斷,它不會立即響應這個中斷請求,而是繼續等待鎖。
? ?- `ReentrantLock`支持中斷操作,當線程在等待鎖時可以響應中斷,并拋出`InterruptedException`。

4. 條件隊列(Condition):
? ?- `synchronized`只能進行簡單的互斥同步,沒有提供類似于條件變量的功能。
? ?- `ReentrantLock`通過`newCondition()`方法創建多個`Condition`實例,可以靈活地控制多線程間的協作,例如掛起和喚醒滿足特定條件的線程。

5. 鎖的嘗試與超時控制:
? ?- `synchronized`不支持嘗試獲取鎖以及設置獲取鎖的超時時間。
? ?- `ReentrantLock`可以通過`tryLock()`、`tryLock(long timeout, TimeUnit unit)`等方法嘗試獲取鎖,或者在指定時間內等待鎖。

6. 鎖的粒度:
? ?- `synchronized`的鎖機制針對的是對象,其鎖的粒度相對較粗。
? ?- `ReentrantLock`可以更精確地控制鎖定范圍,適用于復雜的鎖策略場景。

實現原理:
- `synchronized`由JVM直接支持,底層基于監視器(Monitor)機制,包括進入區、退出區和等待集合,通過monitorenter和monitorexit指令實現加鎖和解鎖操作,且具有內存可見性和原子性保障。
- `ReentrantLock`基于AbstractQueuedSynchronizer(AQS)框架實現,利用CAS操作和CLH(Craig, Landin, and Hagersten)隊列算法,它同樣具備可重入特性,同時通過自定義鎖實現更多的功能擴展。

使用場景:
- `synchronized`適合于簡單的情況,代碼簡潔,性能通常也不錯,在資源競爭不是很激烈的情況下足夠使用,并且異常處理更加安全,因為鎖會在異常情況下自動釋放。
- `ReentrantLock`在復雜并發環境下更有優勢,比如需要控制多個條件的等待/通知,或者需要支持中斷和超時控制,又或者需要更細粒度的鎖控制。在這些場景下,ReentrantLock提供的靈活性和可控性更強。

36、介紹一下ReentrantReadwriteLock?

ReentrantReadWriteLock 是Java并發包 `java.util.concurrent.locks` 中的一個類,它實現了讀寫鎖(Read-Write Lock)的概念。與普通的互斥鎖不同,讀寫鎖允許多個線程同時對共享資源進行讀操作,但在同一時刻只允許一個線程進行寫操作。

特點:
1. 讀寫分離:ReentrantReadWriteLock 包含兩個鎖,一個是讀鎖(也稱為共享鎖),另一個是寫鎖(也稱為獨占鎖)。多個讀線程可以同時持有讀鎖并執行讀取操作,但當有線程持有了寫鎖時,其他所有讀寫線程都必須等待,直到寫鎖被釋放。
2.可重入性:無論是讀鎖還是寫鎖都是可重入的,這意味著如果一個已經持有讀或寫鎖的線程嘗試再次獲取相同的鎖,那么這個請求將成功,并且鎖計數會增加,確保了遞歸調用和嵌套同步的支持。
3. 公平性選擇:ReentrantReadWriteLock 提供了公平和非公平兩種模式的選擇。公平鎖遵循FIFO隊列策略,即等待時間最長的線程優先獲得鎖;而非公平鎖則不保證這種順序,可能會讓新到來的線程插隊獲取鎖,這通常具有更高的性能,但可能導致線程饑餓現象。
4. 狀態管理:ReentrantReadWriteLock 使用內部狀態來跟蹤當前有多少讀線程和寫線程持有鎖,以及每個線程的重入次數。

使用場景:
讀寫鎖非常適合那些讀多寫少的應用場景,例如緩存系統、數據庫訪問等。在這些場景下,讀操作遠比寫操作頻繁,通過允許多個讀線程并發執行,能夠大大提高系統的并發性能和吞吐量。

示例代碼:
```java
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
? ? private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
? ? private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
? ? private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();

? ? public void readOperation() {
? ? ? ? readLock.lock();
? ? ? ? try {
? ? ? ? ? ? // 讀操作代碼塊
? ? ? ? } finally {
? ? ? ? ? ? readLock.unlock();
? ? ? ? }
? ? }

? ? public void writeOperation() {
? ? ? ? writeLock.lock();
? ? ? ? try {
? ? ? ? ? ? // 寫操作代碼塊
? ? ? ? } finally {
? ? ? ? ? ? writeLock.unlock();
? ? ? ? }
? ? }
}
```
在這個例子中,`readOperation()` 方法使用讀鎖保護讀取操作,而 `writeOperation()` 方法使用寫鎖保護寫入操作。在實際應用中,確保在finally塊中解鎖,以防止因異常拋出而導致鎖無法釋放的情況。

37、并發編程解決生產者與消費者模型的常用的幾種方式?

1.synchronized配合wait()與notify()/notify All()方法

2.使用jdk內置的阻塞隊列

3.使用lock瑣以及相關的condition接口,生產者和消費者可以分別調用不同的條件變量的await()方法進入等待狀態,并通過signal()或signalAll()方法喚醒其他線程,以更精細的方式控制線程之間的協作。

4.線程同步工具類:CountDownLatch 、Semaphore、CyclicBarrier

38、什么是可重入瑣與不可重入瑣?

在Java中,可重入鎖(Reentrant Lock)與不可重入鎖是兩種不同類型的鎖機制,它們的主要區別在于線程能否重復獲取已經持有的鎖。

1. 可重入鎖(ReentrantLock)
- 可重入鎖允許同一個線程多次獲取同一把鎖。如果一個線程獲得了某個對象的鎖,并且在沒有釋放該鎖的情況下嘗試再次獲取這把鎖,那么這個線程仍然能夠成功獲取這把鎖。
- Java中的`java.util.concurrent.locks.ReentrantLock`類就實現了可重入鎖的功能。在內部,它會維護一個計數器來記錄當前持有鎖的線程重入的次數,每次獲取鎖時計數器加一,釋放鎖時計數器減一,直到計數器為0時才真正釋放鎖給其他線程競爭。
- 可重入性避免了死鎖的發生,并且支持公平鎖和非公平鎖的選擇以及更靈活的鎖定條件控制。

2. 不可重入鎖
- 不可重入鎖則不允許同一個線程對同一個鎖進行兩次以上的獲取。一旦線程獲取了一個不可重入鎖,如果再嘗試獲取同一把鎖,將會被阻塞或拋出異常,直到當前線程釋放掉已持有的鎖。
- 在Java標準庫中并沒有直接提供不可重入鎖的實現,因為大多數情況下使用可重入鎖更為安全和方便。但理論上可以自己設計實現不可重入的鎖機制,不過這樣做通常不推薦,因為容易導致死鎖和其他并發問題。

對于`synchronized`關鍵字修飾的方法或者代碼塊,在Java中也是可重入的,即當一個線程持有了對象鎖后,仍然可以再次進入由`synchronized`保護的區域。這意味著`synchronized`隱式地提供了可重入鎖的功能。

39、什么是共享鎖與排它鎖?

共享鎖:加瑣后不同線程可以同時進行讀操作,不可以進行寫操作

排它瑣:加瑣后線程可以進行讀寫操作,其它線程將被阻塞

40、什么是自旋鎖?

自旋鎖(Spinlock)是一種簡單的鎖機制,主要用于多線程編程環境中的同步控制。當一個線程試圖獲取已被其他線程持有的自旋鎖時,該線程不會立即進入阻塞狀態并交出CPU執行權,而是會不斷地循環檢查鎖的狀態(即“自旋”),直到鎖變為可用為止。

在自旋期間,請求鎖的線程會持續占用處理器資源,不斷地重復測試和檢查鎖變量,期望在不久的將來能夠獲取到鎖。這種機制適用于等待時間較短且線程切換開銷較大的場景,因為它避免了上下文切換的開銷,提高了在某些特定條件下的并發性能。

然而,如果持有鎖的線程需要很長時間才能釋放鎖,那么自旋鎖會導致請求鎖的線程消耗大量的CPU資源而沒有做任何實際的工作,從而影響系統的整體性能。因此,在設計并發程序時,使用自旋鎖必須謹慎,并結合實際情況考慮是否適合使用,以及如何合理設置自旋次數以防止過度消耗CPU。在許多現代操作系統中,自旋鎖通常和其他更復雜的同步原語(如信號量、互斥鎖等)結合使用,或者僅用于非常小的臨界區代碼片段。

41、Java中Runnable和Callable有什么不同?

Java中的`Runnable`和`Callable`接口都用于定義任務,這些任務可以提交給線程執行,但它們之間存在一些關鍵的區別:

1. Runnable接口

- `java.lang.Runnable`是一個非常基礎且歷史悠久的接口,只有一個`run()`方法。
? ?- `run()`方法沒有返回值,也不拋出受檢異常(checked exception)。
? ?- 通常通過創建實現`Runnable`接口的類實例并傳遞給`Thread`構造器來創建新線程,或者在Executor框架中作為可執行任務提交給線程池。

2. Callable接口
? ?- `java.util.concurrent.Callable`是在Java 5引入并發包時新增的一個接口,提供了更強大的功能。
? ?- `Callable`接口有一個`call()`方法,此方法可以有返回值,并且可以拋出受檢異常。
? ?- 當任務完成時,調用`call()`方法會返回一個泛型結果,這使得它非常適合需要獲取計算結果的任務。
? ?- Callable任務不能直接啟動新的線程,通常通過`FutureTask`包裝后與`Thread`結合使用,或直接提交到`ExecutorService`以異步方式執行,并能通過`Future`對象獲取計算結果以及檢查任務是否已完成。

總結來說,如果你只需要定義一個不返回任何結果、不需要處理異常的任務,那么可以使用`Runnable`。而如果你希望任務能夠返回一個具體的計算結果,并且可能需要拋出異常,那么應該選擇`Callable`接口。此外,`Callable`更適合在高級并發框架如`ExecutorService`中使用,因為它允許你等待任務完成并獲取其結果。

42、什么是FutureTask?

`FutureTask` 是Java并發包 `java.util.concurrent` 中的一個類,它結合了 `Future` 接口和 `Runnable` 接口的功能。`FutureTask` 代表一個可以取消的異步計算任務,它提供了對計算結果的獲取、判斷是否完成以及取消任務的方法。

具體來說:

1. 異步計算:當你提交一個實現了 `Callable` 或 `Runnable` 的任務給 `FutureTask` 后,`FutureTask` 可以在一個獨立線程中執行這個任務。這意味著主線程可以在等待任務完成的同時進行其他操作,而不是阻塞等待任務結束。

2. 結果查詢:通過 `get()` 方法,你可以阻塞地獲取任務的結果(如果任務實現的是 `Callable` 接口)或得知任務是否已經完成(如果任務實現的是 `Runnable` 接口,則 `get()` 返回默認值 `null`)。

3. 取消任務:調用 `cancel(boolean mayInterruptIfRunning)` 方法可以嘗試取消該任務,參數表示是否應該在任務正在運行時中斷它。

4. 狀態檢查:`isDone()` 方法用于檢查任務是否已完成(不論是正常完成還是被取消),而 `isCancelled()` 方法則用來確定任務是否已取消。

5. 可重入性與線程安全性:`FutureTask` 實現了可重入鎖機制,確保任務只能被執行一次,并且其內部狀態管理是線程安全的。

由于 `FutureTask` 兼容 `Runnable` 和 `Future` 接口,它可以方便地與 `ExecutorService` 結合使用,將任務提交到線程池執行,并在之后獲取計算結果。同時,它也支持手動啟動任務,例如直接調用 `run()` 方法來執行任務。

43、如何在Java中獲取線程堆棧?JVM中哪個參數是用來控制線程的棧堆棧小的?

在Java中,可以通過Thread類的getStackTrace()方法來獲取當前線程的堆棧信息。

JVM中用來控制線程棧大小的參數是?-Xss(或?-XX:ThreadStackSize),它用于設置每個線程的Java虛擬機棧空間大小。

44、Thread類中的yield方法有什么作用?

在Java中,`Thread`類中的`yield()`方法是一個靜態 native 方法,它的作用是提示當前正在執行的線程主動讓出CPU執行時間片。調用該方法時,當前線程會暫停自己的執行,并將執行機會交給優先級相同或更高優先級的線程。

然而,需要注意的是:
- `yield()`方法并不保證其他線程能夠立即獲得CPU控制權,因為這完全取決于操作系統的線程調度策略。
- 它只是一個建議性的操作,Java虛擬機(JVM)可以選擇忽略這個提示。
- 使用`yield()`方法并不能確保線程之間的交替執行順序,也無法用于精確控制多線程間的同步。

總的來說,`yield()`方法在多線程編程中主要用于優化程序性能,減少線程間的資源爭搶,特別是在循環體中適當使用可以使得多個線程能更均衡地分享CPU時間,但其效果依賴于具體的系統環境和線程調度器實現,因此不是一個可靠的線程同步機制。在實際開發中,若需要實現更為嚴格的線程同步與通信,通常會采用`synchronized`、`Lock`等更加確定性的同步工具。

45、有三個線程T保它1,T2,T3,怎么確們按順序執行?

1.使用Thread類的join()方法,調用線程等待join()線程執行完成后才繼續執行。

2.synchronized關鍵字瑣與wait()/notify()方法配合使用。

3.ReentranLock配合Condition條件變量的await()與signal()方法。

4.使用線程同步工具類:CountDownLatch、CyclicBarrier、Semaphore

46、一個線程運行時發生異常會怎樣? java中?如何在兩個線程間共享數據?

在Java中,當一個線程在其run()方法或在調用的方法中拋出未捕獲的異常時:

  • 如果沒有為該線程設置UncaughtExceptionHandler,JVM會默認將異常信息輸出到標準錯誤流(System.err),并終止該線程。其他非守護線程不受影響,程序繼續執行。
  • 如果為線程設置了UncaughtExceptionHandler,則當線程拋出未捕獲的異常時,JVM會調用這個異常處理器的uncaughtException(Thread t, Throwable e)方法來處理異常。

多線程共享數據有多種方法,比如:原子類,瑣,線程安全的容器

?47、?Java中notify 和 notifyAll有什么區別? 為什么wait, notify 和 notifyAll這些方法不在thread類里面?

在Java中,`notify()` 和 `notifyAll()` 都是 `java.lang.Object` 類的方法,用于線程間協作和同步。它們的區別在于:

1. notify()
? ?- 當調用一個對象的 `notify()` 方法時,它會喚醒在此對象監視器(鎖)上等待的單個線程。具體喚醒哪一個線程是不確定的,由JVM自行決定。

2. notifyAll()
? ?- 而當調用 `notifyAll()` 方法時,它會喚醒所有在這個對象監視器上等待的線程。這些被喚醒的線程都將變為可運行狀態,并開始競爭獲取該對象的監視器鎖以便繼續執行。

至于為什么 `wait()`, `notify()` 和 `notifyAll()` 這些方法不在 `Thread` 類里面,這是因為這些方法實際上是針對“對象監視器”或“鎖”的操作,而非線程本身。在Java中,每個對象都有一個與之關聯的內置鎖或監視器,當線程進入某個對象的 `synchronized` 方法或代碼塊時,就會獲得這個鎖。因此,這些方法定義在 `Object` 類中,使得任何對象都能調用它們進行線程間的同步和通信。

通過將這些方法設計為 `Object` 類的一部分,意味著任何Java對象都可以作為同步的基礎,并支持線程之間的等待/通知機制。這樣設計更符合面向對象編程的原則,同時也簡化了Java并發模型的設計和實現。

48、Java中堆和棧有什么不同?

Java中堆和棧是兩種不同的內存區域,它們在內存分配、生命周期以及用途上存在顯著區別:

1. 棧(Stack)
? ?- 棧是線程私有的,每個線程都有自己的棧空間。
? ?- 棧用于存儲方法的局部變量、方法參數以及返回地址等信息。
? ?- 棧上的內存分配由編譯器完成,且內存大小在編譯期就已經確定,因此棧內存的空間較小但快速高效。
? ?- 當方法調用結束時,其對應的棧幀會自動彈出棧頂,棧中的局部變量也會隨之銷毀,無需手動釋放。
? ?- 如果棧內存不足(例如遞歸調用過深導致棧溢出),會拋出`StackOverflowError`異常。

2. 堆(Heap)
? ?- 堆是所有線程共享的一塊內存區域,主要用于存放對象實例和數組。
? ?- 在堆中創建的對象具有動態的生命周期,它們的內存分配和回收主要通過JVM的垃圾回收機制進行管理。
? ?- 對象的創建通常使用關鍵字 `new` 進行,內存大小不固定,在運行期間決定,因此可能會面臨內存不足的情況(即OOM:Out of Memory)。
? ?- 垃圾回收器會在特定條件下識別并回收不再使用的對象所占用的內存。不過,由于GC需要耗費時間和資源,堆內存的分配與回收相比棧來說較慢且復雜。
? ?- 若堆內存不足,JVM無法繼續為新對象分配內存時,將拋出`OutOfMemoryError`異常。

總結:
- 棧是程序執行期間臨時存儲數據的地方,特別關注于方法調用過程中的局部變量和控制流程。
- 堆則是Java對象的主要存儲區域,重點關注對象的生命周期管理和內存分配。

49、Java中活鎖和死鎖有什么區別? ?怎么檢測一個線程是否擁有鎖?

在Java中,活鎖和死鎖是兩種不同的并發問題:

  1. 活鎖(Livelock)

    • 活鎖是指兩個或多個線程都處于不斷重試某種操作的狀態,由于每個線程都在等待對方釋放資源以便自己可以繼續執行,但每個線程都不肯讓步,導致所有線程都無法進行任何實際的進展。活鎖中的線程會一直循環執行而無法向前推進。
    • 例如,兩個線程在一個狹窄的走廊相遇,它們都試圖給對方讓路,但是讓的方式相同(比如都向左移動),結果就是雙方一直在原地左右移動,沒有一方能夠通過走廊。
  2. 死鎖(Deadlock)

    • 死鎖是指兩個或多個線程互相持有對方需要的資源,而又都在等待對方釋放資源,從而形成一個僵局,沒有任何線程能夠繼續執行的情況。這種情況下,線程不會像活鎖那樣不斷地嘗試,而是完全停止了進一步的執行,除非外部干預或者系統資源耗盡引發異常。
    • 例如,在銀行轉賬場景中,線程A持有了賬戶B的資金鎖定權并等待賬戶C的資金鎖定權,同時線程B持有了賬戶C的資金鎖定權并等待賬戶A的資金鎖定權,這樣就形成了一個典型的死鎖。

?java.lang.Thread 中有一個方法叫 holdsLock(),它返回 true 如果當且僅當當前線程擁有某個具體對象的鎖。

50、AQS使用了哪些設計模式?AQS 組件了解嗎?

AQS(AbstractQueuedSynchronizer)是Java并發包中用于構建鎖和其他同步組件的基礎框架,它使用了多種設計模式:

1. 模板方法模式:
? ?AQS定義了一系列的抽象方法,如`tryAcquire(int arg)`和`tryRelease(int arg)`等,子類需要重寫這些方法來提供具體的同步策略。而諸如獲取、釋放鎖以及線程排隊等待的公共邏輯則在AQS基類中以模板方法的形式實現。

2. 狀態模式:
? ?AQS內部維護了一個volatile int類型的變量表示同步狀態,通過這個共享的狀態信息可以實現各種不同的同步器行為,比如獨占鎖、共享鎖等。這種對同一接口的不同實現方式體現了狀態模式的思想。

3. 責任鏈模式(或稱為節點鏈表模式):
? ?AQS使用一個FIFO(先進先出)隊列(基于雙向鏈表Node)管理等待獲取資源的線程。當資源不可用時,請求資源的線程會被構造成一個Node并加入到等待隊列尾部,形成一個等待線程的鏈表結構。

4. 迭代器模式(間接體現):
? ?雖然AQS并沒有直接實現迭代器模式,但在其內部處理同步隊列時,實質上是對隊列中的節點進行了類似遍歷的操作,例如在進行取消等待操作時會從隊列頭開始查找待刪除的節點。

5. CAS操作(非設計模式,但重要機制):
? ?AQS大量使用了樂觀鎖機制,即Compare and Swap(CAS),這是一種無鎖編程技術,而非嚴格意義上的設計模式。AQS利用CAS原子性地更新同步狀態,從而避免了傳統的互斥鎖定帶來的性能開銷。

AQS組件:
基于AQS構建的常見組件包括但不限于以下幾種:
- `ReentrantLock`:可重入鎖,支持公平與非公平鎖選擇。
- `Semaphore`:信號量,控制同時訪問特定資源的線程數。
- `CountDownLatch`:計數門栓,允許一個或多個線程等待其他線程完成一組操作后才能繼續執行。
- `CyclicBarrier`:循環柵欄,讓一組線程在一個點上互相等待,直到所有線程都到達柵欄位置后一起繼續執行。
- `ReentrantReadWriteLock`:讀寫鎖,允許多個線程同時讀取共享資源,但在任何時刻只允許一個線程寫入資源。

51、線程池有哪幾種工作隊列?

線程池在Java中通常使用`java.util.concurrent.ExecutorService`及其相關類來實現,其中工作隊列(Work Queue)是線程池中的一個關鍵組件,用于存儲待執行的任務。以下是Java中幾種常見的工作隊列類型:

1. 無界隊列(如:`LinkedBlockingQueue`)
? ?- 無界隊列允許任務無限量地加入到隊列中,只要內存足夠,不會拒絕提交的任務。這意味著如果生產者速度過快,而消費者處理速度跟不上時,隊列可能會持續增長導致內存溢出。因此,在沒有適當控制的情況下,無界隊列可能導致系統資源耗盡。

2. 有界隊列(如:`ArrayBlockingQueue`、`LinkedBlockingDeque`)
? ?- 有界隊列具有預定義的最大容量,當隊列滿時,再嘗試添加任務將會阻塞或拋出異常(取決于線程池配置)。有界隊列有助于防止資源耗盡,因為它可以限制等待執行的任務數量。

3. 優先級隊列(如:`PriorityBlockingQueue`)
? ?- 優先級隊列按照任務的優先級順序進行處理,優先級高的任務會先被執行。它是一個無界的并發隊列,但隊列內的元素需要實現`Comparable`接口或者在構造時提供`Comparator`以確定任務之間的優先級關系。

4. SynchronousQueue
? ?- `SynchronousQueue` 不實際存儲元素,而是將每個插入操作匹配對等的移除操作。這種隊列每次插入必須等待另一個線程的移除操作,反之亦然,非常適合傳遞消息而不是存儲任務的場景。由于其特殊的無緩沖特性,對于線程池而言,一般意味著當任務提交時如果沒有空閑的工作線程立即可用,則會拒絕任務。

通過選擇不同類型的隊列,可以根據應用需求調整線程池的行為和性能。例如,使用無界隊列可以簡化編程模型,但可能增加內存消耗;而使用有界隊列則可以更好地控制系統的負載壓力,并且可以通過調整隊列大小來優化線程池性能。

52.線程池異常怎么處理知道嗎?

1.可以使用try-catch進行異常捕獲與處理

2.也可以定義全局的線程處理器

53.線程池有幾種狀態嗎?

是的,Java中的線程池(具體指`java.util.concurrent.ThreadPoolExecutor`)有多種狀態。線程池的狀態主要通過內部維護的變量來表示,并且這些狀態在執行過程中會發生轉換。以下是ThreadPoolExecutor中定義的主要狀態:

1. RUNNING
? ?- 線程池創建后默認處于運行狀態,能夠接受新任務并執行隊列中的任務。

2. SHUTDOWN
? ?- 當調用`shutdown()`方法后,線程池進入關閉狀態。此時不再接受新的任務提交,但會繼續處理已提交的任務隊列直到清空為止。

3. STOP
? ?- 調用`shutdownNow()`方法后,線程池進入停止狀態。此狀態下不僅不接受新的任務,還會嘗試中斷正在執行的任務,并且不再處理等待隊列中的剩余任務。

4. TIDYING
? ?- 當所有任務都已經終止并且工作隊列為空時,無論是在SHUTDOWN還是STOP狀態下,線程池都會轉換到整理狀態(TIDYING)。在此狀態下,線程池會執行`terminated()`鉤子方法。

5. TERMINATED
? ?- 在TIDYING狀態之后,當`terminated()`方法執行完畢后,線程池就會變成終止狀態(TERMINATED)。此時線程池已經完全停止,不再處理任何任務,也無法再提交新的任務。

這些狀態之間有一定的轉換順序和條件,通常按照正常生命周期從RUNNING開始,最終達到TERMINATED狀態。在不同狀態下,線程池的行為有所不同,例如是否接收新任務、如何處理待執行任務等。

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

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

相關文章

Sora:視頻生成模型作為世界模擬器

我們探索了視頻數據上生成模型的大規模訓練。具體來說&#xff0c;我們在可變持續時間、分辨率和長寬比的視頻和圖像上聯合訓練文本條件擴散模型。我們利用了一個在視頻和圖像潛在碼的時空塊上操作的變壓器架構。我們規模最大的模型 Sora 能夠生成一分鐘的高保真視頻。我們的結…

一周學會Django5 Python Web開發-Django5路由重定向

鋒哥原創的Python Web開發 Django5視頻教程&#xff1a; 2024版 Django5 Python web開發 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili2024版 Django5 Python web開發 視頻教程(無廢話版) 玩命更新中~共計25條視頻&#xff0c;包括&#xff1a;2024版 Django5 Python we…

代碼隨想錄算法訓練營第21天—回溯算法01 | ● 理論基礎 ● *77. 組合

理論基礎 回溯是一種純暴力搜索的方法&#xff0c;它和遞歸相輔相成&#xff0c;通常是執行完遞歸之后緊接著執行回溯相較于以往使用的for循環暴力搜索&#xff0c;回溯能解決更為復雜的問題&#xff0c;如以下的應用場景應用場景 組合問題 如一個集合{1,2,3,4}&#xff0c;找…

alibabacloud學習筆記06(小滴課堂)

講Sentinel流量控制詳細操作 基于并發線程進行限流配置實操 在瀏覽器打開快速刷新會報錯 基于并發線程進行限流配置實操 講解 微服務高可用利器Sentinel熔斷降級規則 講解服務調用常見的熔斷狀態和恢復 講解服務調用熔斷例子 我們寫一個帶異常的接口&#xff1a;

6-7年經驗的前端,回望這些年的風雨,都扛過來了~

前言 回望這6-7年的時光&#xff0c;不覺而已&#xff0c;有種閱盡千帆而過的感覺&#xff0c;可能人總在回頭看一些事情時都會有這種感覺吧。 傻人大學開始接觸計算機行業 大概10年前的我&#xff0c;填好志愿&#xff0c;拿到錄取通知書的那天&#xff0c;命運的齒輪就開始…

基于Spring Boot的學生評獎評優管理系統,計算機畢業設計(帶源碼+論文)

源碼獲取地址&#xff1a; 碼呢-一個專注于技術分享的博客平臺一個專注于技術分享的博客平臺,大家以共同學習,樂于分享,擁抱開源的價值觀進行學習交流http://www.xmbiao.cn/resource-details/1760641819451928577

python子域名收集工具

在網絡安全領域中&#xff0c;發現和管理攻擊面絕對是一項必須的任務&#xff0c;而對域名的尋找和分析是發現攻擊面的重要步驟。今天我們將與您分享關于域名發現的四種方法&#xff0c;并附帶Python示例代碼來幫助您更好的理解和掌握這些方法。 1. 主域名鏈式證書提取域名信息…

MySQL的安裝和備份

一、openEuler 二進制方式安裝MySQL 8.0.x 1、獲取軟件包 [rootLocalhost ~]# wget -c https://mirrors.aliyun.com/mysql/MySQL-8.0/mysql-8.0.28-linux-glibc2.12-x86_64.tar.xz 2、創建用戶和組 [rootLocalhost ~]# groupadd -g 27 -r mysql [rootLocalhost ~]# useradd…

RisingWave的動態過濾器和時間過濾器的用法

動態過濾器 動態過濾器能夠實時過濾數據流&#xff0c;并允許定義傳入數據必須滿足的條件才能進行處理。 動態過濾器demo CREATE TABLE sales(id int ,profit_margin double ,PRIMARY KEY (id) );CREATE TABLE products(product_name string ,product_profit double);--返回…

如何切換到Ubuntu系統上來

上篇講到,使用Ubuntu系統能讓人帶來積極的影響,那么如何使用上這個系統呢?其實很多時候,不是不會安裝的技術問題,而是意愿或者心理障礙的問題。 以下是我使用ubuntu系統一年半的經驗,相信經過這三部分的介紹,可以幫助你了解linux系統的最新進展,克服使用困難,使用上U…

C# 讀取JSON文件

命名空間&#xff1a; using System.Text.Json.Nodes; 讀取JSON&#xff1a; // 讀取設置文件參數 JsonNode json JsonNode.Parse(File.ReadAllText(Environment.CurrentDirectory.Replace("\\bin\\Debug", "") "\\settings.json"))["a…

前端項目git提交規范配置

項目規范管理 目的 為了使團隊多人協作更加的規范&#xff0c;所以需要每次在 git 提交的時候&#xff0c;做一次硬性規范提交&#xff0c;規范 git 的提交信息 使用commitizen規范git提交(交互式提交 自定義提示文案 Commit規范) 安裝依賴 pnpm install -D commitizen c…

visual studio2022使用tensorRT配置

只記錄tensorRT在vs中使用時的配置&#xff0c;下載和安裝的 文章主頁自己尋找。 下載好TensorRT和對應的cuda之后&#xff0c;把tensorRT的鍛煉了和lib文件復制粘貼到cuda對應的文件夾中&#xff0c;以方便調用。 完成之后打開vs新建一個tensorRT的項目&#xff0c;然后開始配…

306_C++_QT_創建多個tag頁面,使用QMdiArea容器控件,每個頁面都是一個新的表格[或者其他]頁面

程序目的是可以打開多個styles文件(int后綴文件),且是tag樣式的(就是可以切多個頁面出來,并且能夠單獨關閉);其中讀取ini文件,將其插入到表格中的操作,也是比較復雜的,因為需要保持RGB字符串和前面的說明字符串對齊 ini文件舉例: [MainMenu] Foreground\Selected=&…

ElasticStack安裝(windows)

官網 : Elasticsearch 平臺 — 大規模查找實時答案 | Elastic Elasticsearch Elastic Stack(一套技術棧) 包含了數據的整合 >提取 >存儲 >使用&#xff0c;一整套! 各組件介紹: beats 套件:從各種不同類型的文件/應用中采集數據。比如:a,b,cd,e,aa,bb,ccLogstash:…

三年功能測試,測試工作吐槽

概述 大家好&#xff0c;我是洋子。有很多粉絲朋友目前還是在做功能測試&#xff0c;日常會遇到很多繁瑣&#xff0c;棘手的問題&#xff0c;今天分享一篇在testerhome社區的帖子《三年功能測試&#xff0c;測試工作吐槽》 原文鏈接https://testerhome.com/topics/38546 這篇文…

vue.js el-tooltip根據文字長度控制是否提示toolTip

一、需求&#xff1a;如何判斷當前文本文字是否超出文本長度&#xff0c;是否需要出現提示toolTip。效果圖如下&#xff1a; 二、實現&#xff1a; 1、表格字段鼠標放置el-popover出現 “引用主題” 的具體內容&#xff1b; <!-- 表格字段&#xff1a;引用主題 --> <…

【web | CTF】攻防世界 Web_php_unserialize

天命&#xff1a;這條反序列化題目也是比較特別&#xff0c;里面的漏洞知識點&#xff0c;在現在的php都被修復了 天命&#xff1a;而且這次反序列化的字符串數量跟其他題目不一樣 <?php class Demo { // 初始化給變量內容&#xff0c;也就是當前文件&#xff0c;高亮顯示…

代碼隨想錄 -- 字符串

文章目錄 反轉字符串描述題解 反轉字符串II描述題解 替換數字描述題解&#xff1a;replace函數題解&#xff1a;雙指針 翻轉字符串里的單詞描述題解 右旋字符串描述題解 實現 strStr()描述題解&#xff1a;暴力算法題解&#xff1a;KMP算法(懵懂) 重復的子字符串描述題解題解&a…

數據備份(上)

備份的意義 數據備份是容災的基礎&#xff0c;防止系統出現操作失誤或者遭受網絡攻擊導致數據丟失&#xff0c;為保證數據安全和業務連續性&#xff0c;有效的防護措施&#xff0c;對數據進行合理的備份、防范于未然。 面臨的威脅 去年2023年10月親自經歷客戶某網站無法訪問…