一、父子線程怎麼共享數據
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原理
-
AQS是一個基於狀態(state)的鏈表管理方式,reentracntlock這個鎖是基於AQS實現的子類sync這個來完成鎖。
-
獲取鎖的時候,當前線程會去更新狀態state的值,如果爲0纔去更新,通過CAS進行更新,如果成功更新爲1,那麼獲取到鎖,將鎖的擁有者改成當前線程,如果失敗,那麼進行tryAcquire() 這個函數進行首先還是嘗試更新state狀態,反正開銷也小,再次去嘗試一次也行,如果嘗試失敗,那麼去看看當前擁有鎖的線程是不是當前線程,如果是,那麼將state狀態值加1,如果不是,那麼將線程入阻塞隊列,addWaiter函數,進行的話首先判斷當前head是不是爲空,爲空嘗試將當前的線程關聯的節點用CAS加入隊列,不爲空或者加入失敗,那麼用CAS加入到隊列的下一個節點。
-
釋放鎖的時候,將狀態值減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表現出了鎖的特性。
整理了學習資料以及學習視頻,送給小夥伴們。點擊【學習資料】自行領取。和一些小夥伴們建了一個技術交流羣,一起探討技術、分享技術資料,旨在共同學習進步,如果感興趣就掃碼加入我們吧!