1. 程式人生 > 其它 >JAVA面試題--Java高併發

JAVA面試題--Java高併發

Java高併發

1.什麼是程序

程序是指執行中的應用程式,每個程序都有自己獨立的地址空間(記憶體空間)。
比如使用者點選桌面的IE瀏覽器,就啟動了一個程序,作業系統就會為該程序分配獨立的地址空間。當用戶再次點選左邊的IE瀏覽器,又啟動了一個程序,作業系統將為新的程序分配新的獨立的地址空間。目前作業系統都支援多程序。

2.什麼是執行緒

程序是表示自願分配的基本單位。而執行緒則是程序中執行運算的最小單位,即執行處理機排程的基本單位。通俗來講:一個程式有一個程序,而一個程序可以有多個執行緒。

3.程序間如何通訊

管道(pipe)

管道是一種半雙工的通訊方式,資料只能單向流動,而且只能在具有親緣關係的程序間使用。程序的親緣關係通常是指父子程序關係
有名管道(namedpipe)

有名管道也是半雙工的通訊方式,但是它云溪無親緣關係程序間的通訊。
訊號量(semaphore)

訊號量是一個計數器,可以用來控制多個程序對共享資源的訪問。它常作為一種鎖機制,防止某程序正在訪問共享資源時,其他程序也訪問該資源。因此,主要作為程序間以及同一程序內不同執行緒之間的同步手段。
訊息佇列(messagequeue)

訊息佇列裡有訊息的連結串列,存放在核心中並由訊息佇列識別符號標識。訊息佇列克服了訊號傳遞訊息少、管道只能承載無格式位元組流以及緩衝區大小受限等缺點
訊號(signal)

訊號是一種比較複雜的通訊方式,用於通知接收程序某個事件已經發生
共享記憶體(shared memory)

共享記憶體就是對映一段能被其他程序所訪問的記憶體,這段共享記憶體由一個程序建立,但多個程序都可以訪問。共享記憶體是最快的IPC方式,它是針對其他程序間通訊方式執行效率低而專門設計的。它往往與其他通訊機制,如訊號量配合使用,來實現程序間的同步和通訊。
套接字(socket)

套介面也是一種程序間通訊機制,以其他通訊機制不同的是,它可用於不同程序間的通訊

4.執行緒間如何通訊

鎖機制:包括互斥鎖、條件變數、讀寫鎖

互斥鎖提供了以排他方式防止資料結構被併發修改的方法
讀寫鎖允許多個執行緒同時讀共享資料,而對寫操作是互斥的
條件變數可以以原子的方式阻塞程序,直到某個特定條件為真為止。對條件的測試是在互斥鎖的保護下進行的。條件變數始終與互斥鎖一起使用。
訊號量機制:包括無名執行緒訊號量和命名執行緒訊號量

訊號機制:類似程序間的訊號處理
執行緒間的通訊目的只要是用於新城同步,所以執行緒沒有像程序通訊中的用於資料交換的通訊機制。

5.同步和非同步有何不同,在什麼情況下分別使用它們?舉例說明

如果資料將線上程間共享。例如:正在寫的資料以後可能會被另一個執行緒讀到,或者正在讀的資料可能已經被另一個執行緒寫過了,那麼這些資料就是共享資料,必須進行同步存取
當應用程式在物件上呼叫了一個需要花費很長時間來執行的方法,並且不希望讓程式等待方法的返回時,就應該使用非同步程式設計,在很多情況下采用非同步途徑往往更有效。
同步互動:指傳送一個請求,需要等待返回,然後才能傳送下一個請求,有個等待的過程
非同步互動:指傳送一個請求,不需要等待返回,隨時可以再發送下一個請求,即不需要等待。
區別:一個需要等待,一個不需要等待

6.程序排程演算法

實時系統:FIFO(First Input First Output,先進先出演算法),SJF(Shortest Job First,最短作業優先演算法),SRTF(Shortest Remaining Time First,最短剩餘時間優先演算法)。
互動式系統:RR(Round Robin,時間片輪轉演算法),HPF(Highest Priority First,最高優先順序演算法),多級佇列,最短程序優先,保證排程,彩票排程,公平分享排程。

7.Java中Unsafe類詳解

  1. 通過Unsafe類可以分配記憶體,可以釋放記憶體;類中提供的3個本地方法allocateMemory、reallocateMemory、freeMemory分別用於分配記憶體,擴充記憶體和釋放記憶體,與C語言中的3個方法對應。
  2. 可以定位物件某欄位的記憶體位置,也可以修改物件的欄位值,即使它是私有的;
  3. 掛起與恢復:將一個執行緒進行掛起是通過park方法實現的,呼叫 park後,執行緒將一直阻塞直到超時或者中斷等條件出現。unpark可以終止一個掛起的執行緒,使其恢復正常。整個併發框架中對執行緒的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,但最終都呼叫了Unsafe.park()方法。
  4. cas
    Java中Unsafe類詳解

8.如何測試併發量?

可以使用apache提供的ab工具測試。

9.有三個執行緒T1,T2,T3,怎麼確保它們按順序執行?

在多執行緒中有多種方法讓執行緒按特定順序執行,你可以用執行緒類的join()方法在一個執行緒中啟動另一個執行緒,另外一個執行緒完成該執行緒繼續執行。為了確保三個執行緒的順序你應該先啟動最後一個(T3呼叫T2,T2呼叫T1),這樣T1就會先完成而T3最後完成。

10.什麼是執行緒排程器(Thread Scheduler)和時間分片(Time Slicing)?

 執行緒排程器是一個作業系統服務,它負責為Runnable狀態的執行緒分配CPU時間。一旦我們建立一個執行緒並啟動它,它的執行便依賴於執行緒排程器的實現。

  時間分片是指將可用的CPU時間分配給可用的Runnable執行緒的過程。分配CPU時間可以基於執行緒優先順序或者執行緒等待的時間。執行緒排程並不受到Java虛擬機器控制,所以由應用程式來控制它是更好的選擇(即最好不要讓你的程式依賴於執行緒的優先順序)。

11.資料庫死鎖?

在執行一個事務時可能要獲取多個鎖,一直持有鎖到事務提交,如果A事務需要獲取的鎖在另一個事務B中,且B事務也在等待A事務所持有的鎖,那麼兩個事務之間就會發生死鎖。但資料庫死鎖比較少見,資料庫會加以干涉死鎖問題,犧牲一個事務使得其他事務正常執行。

12.什麼是鎖順序死鎖?

兩個執行緒試圖以不同的順序獲得相同的鎖,那麼可能發發生死鎖。比如轉賬問題,由from賬戶向to賬戶轉賬,假設每次我們先同步from物件,再同步to賬戶,然後執行轉賬操作,貌似沒什麼問題。如果這時候to賬戶同時向from賬戶轉賬,那麼兩個執行緒可能要永久等待了。

13.死鎖的避免與診斷?

如果一個執行緒最多隻能獲取一個鎖,那麼就不會發生鎖順序死鎖了。如果確實需要獲取多個鎖,鎖的順序可以按照某種規約,比如兩個資源的id值,程式按規約保證獲取鎖的順序一致。或者可以使用顯式的鎖Lock,獲取鎖的時候設定超時時間,超時後可以重新發起,以避免發生死鎖。

14.常見的併發容器?

ConcurrentHashMap:使用了分段鎖,鎖的粒度變得更小,多執行緒訪問時,可能都不存在鎖的競爭,所以大大提高了吞吐量。簡單對比來看,就好比資料庫上用行鎖來取代表鎖,行鎖無疑帶來更大的併發。
CopyOnWriteArrayList:寫入時複製,多執行緒訪問時,彼此不會互相干擾或被修改的執行緒所幹擾,當然copy時有開銷的,尤其時列表元素龐大,且寫入操作頻繁時,所以僅當迭代操作遠遠大於修改操作時,才應該考慮使用。
BlockingQueue:阻塞佇列提供了可阻塞的put和take方法,當佇列已經滿了,那麼put操作將阻塞到佇列可用,當佇列為空時,take操作會阻塞到佇列裡有資料。有界的佇列是一種強大的資源管理器,可以在程式負荷過載時保護應用,可作為一種服務降級的策略。阻塞佇列還提供offer操作,當資料無法加入佇列時,返回失敗狀態,給應用主動處理負荷過載帶來更多靈活性。

15.常見的同步工具類?

CountDownLatch:遞減計數器閉鎖,直到達到某個條件時才放行,多執行緒可以呼叫await方法一直阻塞,直到計數器遞減為零。比如我們連線zookeeper,由於連線操作是非同步的,所以可以使用countDownLatch建立一個計數器為1的鎖,連線掛起,當非同步連線成功時,呼叫countDown通知掛起執行緒;再比如5V5遊戲競技,只有房間人滿了才可以開始遊戲。
FutureTask:帶有計算結果的任務,在計算完成時才能獲取結果,如果計算尚未完成,則阻塞 get
方法。FutureTask將計算結果從執行執行緒傳遞到獲取這個結果的執行緒。
Semaphore:訊號量,用來控制同時訪問某個特定資源的數量,只有獲取到許可acquire,才能夠正常執行,並在完成後釋放許可,acquire會一致阻塞到有許可或中斷超時。使用訊號量可以輕鬆實現一個阻塞佇列。
CyclicBarrier:類似於閉鎖,它可以阻塞一組執行緒,只有所有執行緒全部到達以後,才能夠繼續執行,so執行緒必須相互等待。這在平行計算中是很有用的,將一個問題拆分為多個獨立的子問題,當執行緒到達柵欄時,呼叫await等待,一直阻塞到所有參與執行緒全部到達,再執行下一步任務。

16.Nginx多程序模型是如何實現高併發的?

程序數與併發數不存在很直接的關係。這取決取server採用的工作方式。如果一個server採用一個程序負責一個request的方式,那麼程序數就是併發數。那麼顯而易見的,就是會有很多程序在等待中。等什麼?最多的應該是等待網路傳輸。

Nginx的非同步非阻塞工作方式正是利用了這點等待的時間。在需要等待的時候,這些程序就空閒出來待命了。因此表現為少數幾個程序就解決了大量的併發問題。apache是如何利用的呢,簡單來說:同樣的4個程序,如果採用一個程序負責一個request的方式,那麼,同時進來4個request之後,每個程序就負責其中一個,直至會話關閉。期間,如果有第5個request進來了。就無法及時反應了,因為4個程序都沒幹完活呢,因此,一般有個排程程序,每當新進來了一個request,就新開個程序來處理。nginx不這樣,每進來一個request,會有一個worker程序去處理。但不是全程的處理,處理到什麼程度呢?處理到可能發生阻塞的地方,比如向上遊(後端)伺服器轉發request,並等待請求返回。那麼,這個處理的worker不會這麼傻等著,他會在傳送完請求後,註冊一個事件:“如果upstream返回了,告訴我一聲,我再接著幹”。於是他就休息去了。此時,如果再有request進來,他就可以很快再按這種方式處理。而一旦上游伺服器返回了,就會觸發這個事件,worker才會來接手,這個request才會接著往下走。由於web server的工作性質決定了每個request的大部份生命都是在網路傳輸中,實際上花費在server機器上的時間片不多。這是幾個程序就解決高併發的祕密所在。webserver剛好屬於網路io密集型應用,不算是計算密集型。非同步,非阻塞,使用epoll,和大量細節處的優化。也正是nginx之所以然的技術基石。

17.CopyOnWriteArrayList

CopyOnWriteArrayList : 寫時加鎖,當新增一個元素的時候,將原來的容器進行copy,複製出一個新的容器,然後在新的容器裡面寫,寫完之後再將原容器的引用指向新的容器,而讀的時候是讀舊容器的資料,所以可以進行併發的讀,但這是一種弱一致性的策略。
使用場景:CopyOnWriteArrayList適合使用在讀操作遠遠大於寫操作的場景裡,比如快取。

18.AQS

  1. AQS使用一個int成員變數來表示同步狀態,通過內建的FIFO佇列來完成獲取資源執行緒的排隊工作。
private volatile int state;//共享變數,使用volatile修飾保證執行緒可見性
  • 1
  1. 2種同步方式:獨佔式,共享式。獨佔式如ReentrantLock,共享式如Semaphore,CountDownLatch,組合式的如ReentrantReadWriteLock

  2. 節點的狀態
    CANCELLED,值為1,表示當前的執行緒被取消;
    SIGNAL,值為-1,表示當前節點的後繼節點包含的執行緒需要執行,也就是unpark;
    CONDITION,值為-2,表示當前節點在等待condition,也就是在condition佇列中;
    PROPAGATE,值為-3,表示當前場景下後續的acquireShared能夠得以執行;
    值為0,表示當前節點在sync佇列中,等待著獲取鎖。

  3. 模板方法模式

     protected boolean tryAcquire(int arg) : 獨佔式獲取同步狀態,試著獲取,成功返回true,反之為false

     protected boolean tryRelease(int arg) :獨佔式釋放同步狀態,等待中的其他執行緒此時將有機會獲取到同步狀態;

     protected int tryAcquireShared(int arg) :共享式獲取同步狀態,返回值大於等於0,代表獲取成功;反之獲取失敗;

     protected boolean tryReleaseShared(int arg) :共享式釋放同步狀態,成功為true,失敗為false

    AQS維護一個共享資源state,通過內建的FIFO來完成獲取資源執行緒的排隊工作。該佇列由一個一個的Node結點組成,每個Node結點維護一個prev引用和next引用,分別指向自己的前驅和後繼結點。雙端雙向連結串列。

    1. 獨佔式:樂觀的併發策略
      acquire
       a.首先tryAcquire獲取同步狀態,成功則直接返回;否則,進入下一環節;
      b.執行緒獲取同步狀態失敗,就構造一個結點,加入同步佇列中,這個過程要保證執行緒安全;
       c.加入佇列中的結點執行緒進入自旋狀態,若是老二結點(即前驅結點為頭結點),才有機會嘗試去獲取同步狀態;否則,當其前驅結點的狀態為SIGNAL,執行緒便可安心休息,進入阻塞狀態,直到被中斷或者被前驅結點喚醒。
      release
      release的同步狀態相對簡單,需要找到頭結點的後繼結點進行喚醒,若後繼結點為空或處於CANCEL狀態,從後向前遍歷找尋一個正常的結點,喚醒其對應執行緒。
  4. 共享式:
    共享式地獲取同步狀態.同步狀態的方法tryAcquireShared返回值為int。
    a.當返回值大於0時,表示獲取同步狀態成功,同時還有剩餘同步狀態可供其他執行緒獲取;
     b.當返回值等於0時,表示獲取同步狀態成功,但沒有可用同步狀態了;
     c.當返回值小於0時,表示獲取同步狀態失敗。

  5. AQS實現公平鎖和非公平鎖
    非公平鎖中,那些嘗試獲取鎖且尚未進入等待佇列的執行緒會和等待佇列head結點的執行緒發生競爭。公平鎖中,在獲取鎖時,增加了isFirst(current)判斷,當且僅當,等待佇列為空或當前執行緒是等待佇列的頭結點時,才可嘗試獲取鎖。
     Java併發包基石-AQS詳解

19.Java裡的阻塞佇列

7個阻塞佇列。分別是

ArrayBlockingQueue :一個由陣列結構組成的有界阻塞佇列。
LinkedBlockingQueue :一個由連結串列結構組成的有界阻塞佇列。
PriorityBlockingQueue :一個支援優先順序排序的無界阻塞佇列。
DelayQueue:一個使用優先順序佇列實現的無界阻塞佇列。
SynchronousQueue:一個不儲存元素的阻塞佇列。
LinkedTransferQueue:一個由連結串列結構組成的無界阻塞佇列。
LinkedBlockingDeque:一個由連結串列結構組成的雙向阻塞佇列。

新增元素

Java中的阻塞佇列介面BlockingQueue繼承自Queue介面。BlockingQueue介面提供了3個新增元素方法。
add:新增元素到佇列裡,新增成功返回true,由於容量滿了新增失敗會丟擲IllegalStateException異常
offer:新增元素到佇列裡,新增成功返回true,新增失敗返回false
put:新增元素到佇列裡,如果容量滿了會阻塞直到容量不滿

刪除方法

3個刪除方法
poll:刪除佇列頭部元素,如果佇列為空,返回null。否則返回元素。
remove:基於物件找到對應的元素,並刪除。刪除成功返回true,否則返回false
take:刪除佇列頭部元素,如果佇列為空,一直阻塞到佇列有元素並刪除

20.Fork/Join框架

Fork/Join框架是Java 7提供的一個用於並行執行任務的框架,是一個把大任務分割成若干個小任務,最終彙總每個小任務結果後得到大任務結果的框架。Fork/Join框架要完成兩件事情:

  1.任務分割:首先Fork/Join框架需要把大的任務分割成足夠小的子任務,如果子任務比較大的話還要對子任務進行繼續分割

  2.執行任務併合並結果:分割的子任務分別放到雙端佇列裡,然後幾個啟動執行緒分別從雙端佇列裡獲取任務執行。子任務執行完的結果都放在另外一個佇列裡,啟動一個執行緒從佇列裡取資料,然後合併這些資料。

  在Java的Fork/Join框架中,使用兩個類完成上述操作

  1.ForkJoinTask:我們要使用Fork/Join框架,首先需要建立一個ForkJoin任務。該類提供了在任務中執行fork和join的機制。通常情況下我們不需要直接整合ForkJoinTask類,只需要繼承它的子類,Fork/Join框架提供了兩個子類:

    a.RecursiveAction:用於沒有返回結果的任務

    b.RecursiveTask:用於有返回結果的任務

  2.ForkJoinPool:ForkJoinTask需要通過ForkJoinPool來執行

  任務分割出的子任務會新增到當前工作執行緒所維護的雙端佇列中,進入佇列的頭部。當一個工作執行緒的佇列裡暫時沒有任務時,它會隨機從其他工作執行緒的佇列的尾部獲取一個任務(工作竊取演算法)。
Fork/Join框架的實現原理
  ForkJoinPool由ForkJoinTask陣列和ForkJoinWorkerThread陣列組成,ForkJoinTask陣列負責將存放程式提交給ForkJoinPool,而ForkJoinWorkerThread負責執行這

線上刷題小程式

參考連結

https://blog.csdn.net/weixin_41050155/article/details/88047556

https://www.cnblogs.com/jjunior/p/14113248.html

https://www.cnblogs.com/yang-yutao/p/11553044.html

https://blog.csdn.net/qq_20980207/article/details/98846287

https://github.com/JavaInterviewHub/JavaInterview/blob/main/Java高併發.md