【精選面試題】多線程系列

一、父子線程怎麼共享數據

JDK的InheritableThreadLocal類可以完成父線程到子線程的值傳遞。但對於使用線程池等會池化複用線程的組件的情況,線程由線程池創建好,並且線程是池化起來反覆使用的;這時父子線程關係的ThreadLocal值傳遞已經沒有意義,應用需要的實際上是把任務提交給線程池時的ThreadLocal值傳遞到任務執行時。

核心類TransmittableThreadLocal

public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> { ...... }

首先TransmittableThreadLocal繼承自InheritableThreadLocal,這樣可以在不破壞原有InheritableThreadLocal特性的情況下,還能充分使用Thread線程創建過程中執行init方法,從而達到父子線程傳遞數據的目的。

變量holder源碼如下

  • holder中存放的是InheritableThreadLocal本地變量

  • WeakHashMap支持存放空置

// 理解holder,需注意如下幾點:
// 1、holder 是 InheritableThreadLocal 變量;
// 2、holder 是 static 變量;
// 3、value 是 WeakHashMap;
// 4、深刻理解 ThreadLocal 工作原理;
private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder =
    new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
      @Override
      protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
          return new WeakHashMap<>();
      }

      @Override
      protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
          return new WeakHashMap<>(parentValue);
      }
};

 

主要方法源碼如下

  • get方法調用時,先獲取父類的相關數據判斷是否有數據,然後在holder中把自身也給加進去

  • set方法調用時,先在父類中設置,再本地判斷是holder否爲刪除或者是新增數據

  • remove調用時,先刪除自身,再刪除父類中的數據,刪除也是直接以自身this作爲變量Key

// 調用 get() 方法時,同時將 this 指針放入 holder
public final T get() {
    T value = super.get();
    if (null != value) {
        addValue();
    }
    return value;
}
void addValue() {
    if (!holder.get().containsKey(this)) {
        holder.get().put(this, null); // WeakHashMap supports null value.
    }
}
// 調用 set() 方法時,同時處理 holder 中 this 指針
public final void set(T value) {
    super.set(value);
    if (null == value) { // may set null to remove value
        removeValue();
    } else {
        addValue();
    }
}
void removeValue() {
    holder.get().remove(this);
}

 

採用包裝的形式來處理線程池中的線程不會執行初始化的問題,源碼如下:

  • 先取得holder

  • 備份線程本地數據

  • run原先的方法

  • 還原線程本地數據

public void run() {
     Object captured = this.capturedRef.get():
     if (captured != null && (!this.releaseTtlValueReferenceAfterRun ||
             this.capturedRef.compareAndSet(captured, (0bject) null))){
         Object backup = Transmitter.replay(captured);
         try {
             this.runnable.run();
         } finally {
             Transmitter.restore(backup);
         }
     }else{
         throw new IllegalStateException("TTL value reference is released after run!");
     }
}

 

處理線程池中的線程不會執行初始化的問題,備份方法:

  • 先獲取holder中的數據

  • 進行迭代,數據在captured中不存在,但是holder中存在,說明是後來加進去的,進行刪除

  • 再將captured設置到當前線程中

public static Object repLay(@Nonnull Object captured) (
    Map<TransmittableThreadLocal<?>,Object>capturedMap = (Map) captured;
    Map<TransmittableThreadLocal<?>,Object>backup = new HashMap() ;
    Iterator iterator=((Map) TransmittableThreadLocal.holder.get()).entrySet().iterator();

    while(iterator.has Next() ) (
       Entry<TransmittableThreadLocal<?>,?>next = (Entry) iterator.next()
       TransmittableThreadLocal<?>threadLocal = (TransmittableThreadLocal)next.getKey();
       backup.put(threadLocal, threadLocal.get() ) ;
       if(!capturedMap.containsKey(thread Local) ) (
           iterator.remove() ;
           threadLocal.superRemove() ;
       )

    setTtlValuesTo(capturedMap) ;
    TransmittableThreadLocal.doExecuteCallback(true) ;
    return backup;
)

 

還原方法

  • 先獲取holder中的數據

  • backup中不存在,holder中存在,說明是後面加進去的,進行刪除還原操作

  • 再將backup設置到當前線程中

public static void restore(@Nonnull0bject backup) (
    Map<TransmittableThreadLocal<?>,Object> backupMap = (Map)backup;
    TransmittableThreadLocal.doExecuteCallback(false)
    Iterator iterator=((Map) TransmittableThreadLocal.holder.get()).entrySet().iterator();

    while(iterator.has Next() ) (
        Entry<TransmittableThreadLocal<?>,?> next = (Entry)iterator.next();
        TransmittableThreadLocal<?> threadLocaL=(TransmittableThreadLocal)next.getKey();
        if(!backupMap.containsKey(threadLocal) ) (
            iterator.remove() ;
            threadLocal.superRemove() ;
        }
    |
    setTtlValuesTo(backupMap) ;
}

 

二、CountDownLatch和CyclicBarrier的異同

1、相同點

   都可以實現線程間的等待

2、不同點

   側重點不同

  • CountDownLatch:一般用於一個線程等待一組其它線程;計數器不可以重用

  • CyclicBarrier:一般是一組線程間的相互等待至某同步點,計數器是可以重用的

   實現原理不同

  • CyclicBarrier:如果有三個線程thread1、thread2和thread3,假設線程執行順序是thread1、thread2、thread3,那麼thread1、thread2對應的Node節點會被加入到Condition等待隊列中,當thread3執行的時候,會將thread1、thread2對應的Node節點按thread1、thread2順序轉移到AQS同步隊列中,thread3執行lock.unlock()的時候,會先喚醒thread1,thread1恢復繼續執行,thread1執行到lock.unlock()的時候會喚醒thread2恢復執行

  • CountDownLatch:使用CountDownLatch(int count)構造器創建CountDownLatch實例 ,將count參數賦值給內部計數 state,調 await() 法阻塞當前線程,並將當前線程封裝加到等待隊 中,直到state等於零或當前線程被中斷;調 countDown() 法使state值減 ,如果state等於零則喚醒等待隊中的線程

 

三、AQS原理

  1. AQS是一個基於狀態(state)的鏈表管理方式,reentracntlock這個鎖是基於AQS實現的子類sync這個來完成鎖。

  2. 獲取鎖的時候,當前線程會去更新狀態state的值,如果爲0纔去更新,通過CAS進行更新,如果成功更新爲1,那麼獲取到鎖,將鎖的擁有者改成當前線程,如果失敗,那麼進行tryAcquire() 這個函數進行首先還是嘗試更新state狀態,反正開銷也小,再次去嘗試一次也行,如果嘗試失敗,那麼去看看當前擁有鎖的線程是不是當前線程,如果是,那麼將state狀態值加1,如果不是,那麼將線程入阻塞隊列,addWaiter函數,進行的話首先判斷當前head是不是爲空,爲空嘗試將當前的線程關聯的節點用CAS加入隊列,不爲空或者加入失敗,那麼用CAS加入到隊列的下一個節點。

  3. 釋放鎖的時候,將狀態值減1,如果狀態值爲0說明可以釋放鎖,如果結果狀態爲0,就將排它鎖的Owner設置爲null,以使得其它的線程有機會進行執行。

 

四、volatile(指令重排序和內存屏障)

1、什麼是內存屏障

內存屏障其實就是一個CPU指令,在硬件層面上來說可以扥爲兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障。主要有兩個作用:

(1)阻止屏障兩側的指令重排序;

(2)強制把寫緩衝區/高速緩存中的髒數據等寫回主內存,讓緩存中相應的數據失效。

 

在JVM層面上來說作用與上面的一樣,但是種類可以分爲四種:

 

2、volatile如何保證有序性

首先一個變量被volatile關鍵字修飾之後有兩個作用:

(1)對於寫操作:對變量更改完之後,要立刻寫回到主存中。

(2)對於讀操作:對變量讀取的時候,要從主內存中讀,而不是緩存。

現在針對上面JVM的四種內存屏障,應用到volatile身上。因此volatile也帶有了這種效果。其實上面提到的這些內存屏障應用的效果,可以用happen-before來總結歸納。

 

3、內存屏障分類

內存屏障有三種類型和一種僞類型:

(1)lfence:即讀屏障(Load Barrier),在讀指令前插入讀屏障,可以讓高速緩存中的數據失效,重新從主內存加載數據,以保證讀取的是最新的數據。

(2)sfence:即寫屏障(Store Barrier),在寫指令之後插入寫屏障,能讓寫入緩存的最新數據寫回到主內存,以保證寫入的數據立刻對其他線程可見。

(3)mfence,即全能屏障,具備ifence和sfence的能力。

(4)Lock前綴:Lock不是一種內存屏障,但是它能完成類似全能型內存屏障的功能。

 

爲什麼說Lock是一種僞類型的內存屏障,是因爲內存屏障具有happen-before的效果,而Lock在一定程度上保證了先後執行的順序,因此也叫做僞類型。比如,IO操作的指令,當指令不執行時,就具有了mfence的功能。

 

由於內存屏障的作用,避免了volatile變量和其它指令重排序、線程之間實現了通信,使得volatile表現出了鎖的特性。

整理了學習資料以及學習視頻,送給小夥伴們。點擊【學習資料】自行領取。和一些小夥伴們建了一個技術交流羣,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就掃碼加入我們吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章