高併發編程基礎知識

4)線程和進程的區別:(必考)

答:java

  1. 進程是一個 「執行中的程序」,是系統進行資源分配和調度的一個獨立單位;
  2. 線程是進程的一個實體,一個進程中擁有多個線程,線程之間共享地址空間和其它資源(因此通訊和同步等操做線程比進程更加容易);
  3. 線程上下文的切換比進程上下文切換要快不少。
    • (1)進程切換時,涉及到當前進程的 CPU 環境的保存和新被調度運行進程的 CPU 環境的設置。程序員

    • (2)線程切換僅須要保存和設置少許的寄存器內容,不涉及存儲管理方面的操做。面試

面試官:進程間如何通信?線程間如何通信?緩存

答:進程間通信依靠 IPC 資源,例如管道(pipes)、套接字(sockets)等;安全

線程間通信依靠 JVM 提供的 API,例如 wait()、notify()、notifyAll() 等方法,線程間還能夠經過共享的主內存來進行值的傳遞。多線程

 

什麼是阻塞(Blocking)和非阻塞(Non-Blocking)?

答:阻塞和非阻塞一般用來形容多線程間的相互影響。好比一個線程佔用了臨界區資源,那麼其餘全部須要這個而資源的線程就必須在這個臨界區中進行等待。等待會致使線程掛起,這種狀況就是阻塞。此時,若是佔用資源的線程一直不肯意釋放資源,那麼其餘全部阻塞在這個臨界區上的線程都不能工做。框架

非阻塞的意思與之相反,它強調沒有一個線程能夠妨礙其餘線程執行。全部的線程都會嘗試不斷前向執行。socket

 

臨界區是什麼?分佈式

答:臨界區用來表示一種公共資源或者說是共享資源,能夠被多個線程使用。可是每一次,只能有一個線程使用它,一旦臨界區資源被佔用,其餘線程要想使用這個資源,就必須等待。spa

 

什麼是死鎖(Deadlock)、飢餓(Starvation)和活鎖(Livelock

死鎖應該是最糟糕的一種狀況了,它表示兩個或者兩個以上的進程在執行過程當中,因爲競爭資源或者因爲彼此通訊而形成的一種阻塞的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。

 

飢餓是指某一個或者多個線程由於種種緣由沒法得到所須要的資源,致使一直沒法執行。好比:
1)它的線程優先級可能過低,而高優先級的線程不斷搶佔它須要的資源,致使低優先級的線程沒法工做。在天然界中,母雞餵食雛鳥時,很容易出現這種狀況,因爲雛鳥不少,食物有限,雛鳥之間的食物競爭可能很是厲害,小雛鳥由於常常搶不到食物,有可能會被餓死。線程的飢餓也很是相似這種狀況。
2)另一種多是,某一個線程一直佔着關鍵資源不放,致使其餘須要這個資源的線程沒法正常執行,這種狀況也是飢餓的一種。
與死鎖相比,飢餓仍是有可能在將來一段時間內解決的(好比高優先級的線程已經完成任務,再也不瘋狂的執行)

 

活鎖是一種很是有趣的狀況。不知道你們是否是有遇到過這樣一種狀況,當你要坐電梯下樓,電梯到了,門開了,這時你正準備出去,但不巧的是,門外一我的擋着你的去路,他想進來。因而你很紳士的靠左走,避讓對方,但同時對方也很紳士,但他靠右走但願避讓你。結果,大家又撞上了。因而乎,大家都意識到了問題,但願儘快避讓對方,你當即向右走,他也當即向左走,結果又撞上了!不過介於人類的只能,我相信這個動做重複 二、 3 次後,你應該能夠順利解決這個問題,由於這個時候,你們都會本能的對視,進行交流,保證這種狀況再也不發生。
但若是這種狀況發生在兩個線程間可能就不會那麼幸運了,若是線程的智力不夠,且都秉承着 「謙讓」 的原則,主動將資源釋放給他人使用,那麼就會出現資源不斷在兩個線程中跳動,而沒有一個線程能夠同時拿到全部的資源而正常執行。這種狀況就是活鎖。

 

如何避免死鎖?(常常接着問這個問題哦~)

答:指定獲取鎖的順序,舉例以下:

  1. 好比某個線程只有得到 A 鎖和 B 鎖才能對某資源進行操做,在多線程條件下

  2. 得到鎖的順序是必定的,好比規定,只有得到 A 鎖的線程纔有資格獲取 B 鎖,按順序獲取鎖就能夠避免死鎖!!!

  1. 新建(NEW)狀態:表示新建立了一個線程對象,而此時線程並無開始執行。

  2. 可運行(RUNNABLE)狀態:線程對象建立後,其它線程(好比 main 線程)調用了該對象的 start() 方法,才表示線程開始執行。當線程執行時,處於 RUNNBALE 狀態,表示線程所需的一切資源都已經準備好了。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取 cpu 的使用權。

  3. 阻塞(BLOCKED)狀態:若是線程在執行過程終於到了 synchronized 同步塊,就會進入 BLOCKED 阻塞狀態,這時線程就會暫停執行,直到得到請求的鎖。

  4. 等待(WAITING)狀態:當線程等待另外一個線程通知調度器一個條件時,它本身進入等待狀態。在調用Object.wait方法或Thread.join方法,或者是等待java.util.concurrent庫中的Lock或Condition時,就會出現這種狀況;

  5. 計時等待(TIMED_WAITING)狀態:Object.wait、Thread.join、Lock.tryLock和Condition.await 等方法有超時參數,還有 Thread.sleep 方法、LockSupport.parkNanos 方法和 LockSupport.parkUntil 方法,這些方法會致使線程進入計時等待狀態,若是超時或者出現通知,都會切換會可運行狀態;

  6. 終止(TERMINATED)狀態:當線程執行完畢,則進入該狀態,表示結束。

注意:從 NEW 狀態出發後,線程不能再回到 NEW 狀態,同理,處於 TERMINATED 狀態的線程也不能再回到 RUNNABLE 狀態。

 

  1. sleep 方法:是 Thread 類的靜態方法,當前線程將睡眠 n 毫秒,線程進入阻塞狀態。當睡眠時間到了,會解除阻塞,進行可運行狀態,等待 CPU 的到來。睡眠不釋放鎖(若是有的話);

  2. wait 方法:是 Object 的方法,必須與 synchronized 關鍵字一塊兒使用,線程進入阻塞狀態,當 notify 或者 notifyall 被調用後,會解除阻塞。可是,只有從新佔用互斥鎖以後纔會進入可運行狀態。睡眠時,釋放互斥鎖。

2)synchronized 關鍵字:

答:底層實現:

  1. 進入時,執行 monitorenter,將計數器 +1,釋放鎖 monitorexit 時,計數器-1;

  2. 當一個線程判斷到計數器爲 0 時,則當前鎖空閒,能夠佔用;反之,當前線程進入等待狀態。

含義:(monitor 機制)

Synchronized 是在加鎖,加對象鎖。對象鎖是一種重量鎖(monitor),synchronized 的鎖機制會根據線程競爭狀況在運行時會有偏向鎖(單一線程)、輕量鎖(多個線程訪問 synchronized 區域)、對象鎖(重量鎖,多個線程存在競爭的狀況)、自旋鎖等。

 

4)volatile 能使得一個非原子操做變成原子操做嗎?

答:能。

一個典型的例子是在類中有一個 long 類型的成員變量。若是你知道該成員變量會被多個線程訪問,如計數器、價格等,你最好是將其設置爲 volatile。爲何?由於 Java 中讀取 long 類型變量不是原子的,須要分紅兩步,若是一個線程正在修改該 long 變量的值,另外一個線程可能只能看到該值的一半(前 32 位)。可是對一個 volatile 型的 long 或 double 變量的讀寫是原子。

 核心線程池內部實現

 

2)多線程中的忙循環是什麼?

答:忙循環就是程序員用循環讓一個線程等待,不像傳統方法 wait(),sleep() 或yield() 它們都放棄了 CPU 控制權,而忙循環不會放棄 CPU,它就是在運行一個空循環。這麼作的目的是爲了保留 CPU 緩存

在多核系統中,一個等待線程醒來的時候可能會在另外一個內核運行,這樣會重建緩存,爲了不重建緩存和減小等待重建的時間就可使用它了。

你是如何調用 wait()方法的?使用 if 塊仍是循環?爲何?

答:wait() 方法應該在循環調用,由於當線程獲取到 CPU 開始執行的時候,其餘條件可能尚未知足,因此在處理前,循環檢測條件是否知足會更好。下面是一段標準的使用 wait 和 notify 方法的代碼:

 

 

4.雙重校驗鎖

爲了達到線程安全,又能提升代碼執行效率,咱們這裏能夠採用DCL的雙檢查鎖機制來完成,代碼實現以下:

public class Singleton { private static Singleton singleton; private Singleton() { } public static Singleton getInstance(){ if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } } 

這種是用雙重判斷來建立一個單例的方法,那麼咱們爲何要使用兩個if判斷這個對象當前是否是空的呢 ?由於當有多個線程同時要建立對象的時候,多個線程有可能都中止在第一個if判斷的地方,等待鎖的釋放,而後多個線程就都建立了對象,這樣就不是單例模式了,因此咱們要用兩個if來進行這個對象是否存在的判斷。

 

 

 

 

 

 

volatile 修飾符的有過什麼實踐?

答:

  1. 一種實踐是用 volatile 修飾 long 和 double 變量,使其能按原子類型來讀寫。double 和 long 都是64位寬,所以對這兩種類型的讀是分爲兩部分的,第一次讀取第一個 32 位,而後再讀剩下的 32 位,這個過程不是原子的,但 Java 中 volatile 型的 long 或 double 變量的讀寫是原子的。

  2. volatile 修復符的另外一個做用是提供內存屏障(memory barrier),例如在分佈式框架中的應用。簡單的說,就是當你寫一個 volatile 變量以前,Java 內存模型會插入一個寫屏障(write barrier),讀一個 volatile 變量以前,會插入一個讀屏障(read barrier)。意思就是說,在你寫一個 volatile 域時,能保證任何線程都能看到你寫的值,同時,在寫以前,也能保證任何數值的更新對全部線程是可見的,由於內存屏障會將其餘全部寫的值更新到緩存。

  3. 5)ThreadLocal(線程局部變量)關鍵字:

    1. 答:當使用 ThreadLocal 維護變量時,其爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立的改變本身的副本,而不會影響其餘線程對應的副本。

      ThreadLocal 內部實現機制:

      1. 每一個線程內部都會維護一個相似 HashMap 的對象,稱爲 ThreadLocalMap,裏邊會包含若干了 Entry(K-V 鍵值對),相應的線程被稱爲這些 Entry 的屬主線程;

      2. Entry 的 Key 是一個 ThreadLocal 實例,Value 是一個線程特有對象。Entry 的做用便是:爲其屬主線程創建起一個 ThreadLocal 實例與一個線程特有對象之間的對應關係;

      3. Entry 對 Key 的引用是弱引用;Entry 對 Value 的引用是強引用。

        5)ThreadLocal(線程局部變量)關鍵字:

        答:當使用 ThreadLocal 維護變量時,其爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立的改變本身的副本,而不會影響其餘線程對應的副本。

        ThreadLocal 內部實現機制:

        1. 每一個線程內部都會維護一個相似 HashMap 的對象,稱爲 ThreadLocalMap,裏邊會包含若干了 Entry(K-V 鍵值對),相應的線程被稱爲這些 Entry 的屬主線程;

        2. Entry 的 Key 是一個 ThreadLocal 實例,Value 是一個線程特有對象。Entry 的做用便是:爲其屬主線程創建起一個 ThreadLocal 實例與一個線程特有對象之間的對應關係;

        3. Entry 對 Key 的引用是弱引用;Entry 對 Value 的引用是強引用。

相關文章
相關標籤/搜索