多線程共享變量的維護是很是頭痛的問題,採用樂觀悲觀策略,悲觀策略簡單地作法咱們能夠對共享變量加鎖實現,可是鎖的開銷是比較大的,所以咱們也能夠經過樂觀策略,採用相似CAS(Compare And Set)
的方法進行維護,固然,在讀多寫少的狀況下,咱們還能夠採用Copy-On-Write寫時複製
的來控制共享變量,其中最經典的實現那就是JDK的CopyOnWriteArrayList
了。java
好了,說了這麼多,下面正式進入正題。咱們能夠想這麼一個問題,在多線程環境中如何對每一條線程的生命週期進行跟蹤,跟具體地說,在web
項目中,咱們一般會對某一個請求從進入系統到退出系統的整個生命週期進行日誌記錄,又或者說是對一個事務的全過程進行跟蹤記錄,一般的作法是採用爲每一個線程創建一個叫transactionId
的值,用於標識每一個線程並進程跟蹤,下圖紅圈即爲transactionId
的值。web
那麼你可能會說,我也能夠不使用transactionId
來跟蹤啊,我採用線程名不就行了嗎?道理是這樣,可是當你想對改值進行自定義呢?好比獲取客戶端的ip地址、被調用的接口名呢???數據庫
好了,假設咱們採用transactionId
來跟蹤線程,那麼若是這個transactionId
是個共享變量的話,那咱們不就是得對其作相關線程同步的操做,線程那麼多,那不煩死,笨死!嗯,TheadLocal
就是在這種狀況下而生了,一個TheadLocal
表明一個變量,你千萬不要覺得它表明一個線程,否則我會暈死!這個變量天生就是線程安全的。爲何呢?由於它的工做原理是會爲每條線程作一份變量的拷貝,各個線程的變量不會相互影響,天然就不會有線程安全問題咯!安全
醬紫,我畫張圖吧!bash
TheadLocal
的底層實現原理是經過ThreadLocalMap
實現,時間匆忙,其原理不在本文講解範圍內,讀者自行學習源碼!還有一點思想值得學習,多線程通常的同步機制採用了「以時間換空間」的方式,好比定義一個static變量,同步訪問,而ThreadLocal則採用了「以空間換時間」的方式。session
package com.wokao66.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadLocalTest {
//線程本地變量
private static ThreadLocal<Integer> sessionId = new ThreadLocal<>();
public static void main(String[] args) {
//線程1
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//我對ThreadLocal的值進行修改,自定義
sessionId.set(1);
try {
Thread.sleep(1000);
//模擬一個線程的生命週期屢次獲取ThreadLocal變量的值,驗證是否在當前線程有作一份拷貝
for (int i = 0; i < 5; i++) {
System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
}
} catch (InterruptedException e) {}
}
});
//線程2
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//我對ThreadLocal的值進行修改,自定義
sessionId.set(2);
try {
Thread.sleep(1000);
//模擬一個線程的生命週期屢次獲取ThreadLocal變量的值,驗證是否在當前線程有作一份拷貝
for (int i = 0; i < 5; i++) {
System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
}
} catch (InterruptedException e) {}
}
});
//線程1
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
//我對ThreadLocal的值進行修改,自定義
sessionId.set(3);
try {
Thread.sleep(1000);
//模擬一個線程的生命週期屢次獲取ThreadLocal變量的值,驗證是否在當前線程有作一份拷貝
for (int i = 0; i < 5; i++) {
System.err.println("[" + Thread.currentThread().getName() + "]" + sessionId.get());
}
} catch (InterruptedException e) {}
}
});
ExecutorService service = Executors.newFixedThreadPool(30);
service.execute(thread1);
service.execute(thread2);
service.execute(thread3);
}
}
複製代碼
控制檯輸出多線程
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-1]1
[pool-1-thread-2]2
[pool-1-thread-2]2
[pool-1-thread-2]2
[pool-1-thread-1]1
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
[pool-1-thread-3]3
複製代碼
能夠看到每條線程對應的ThreadLocal
是同樣的,證實了確實是有作拷貝操做!ide
使用ThreadLocal
首先一點那就是對變量的訪問不須要同步控制,經常使用的場景以下:性能
MDC
機制須要注意的是,既然TreadLocal
是以採用空間換取時間的思想,因此能夠想象TreadLocal
並不適合來定義大對象,由於大對象的話每一個線程都有拷貝,線程一多,性能一定受到牽連,甚至JVM拋出ERROR
學習