1. 程式人生 > 其它 >Java ThreadLocal詳解

Java ThreadLocal詳解

技術標籤:java多執行緒高併發javaredis多執行緒

ThreadLocal,執行緒本地變數,作為多執行緒程式設計中很重要的一個元件,有很多的應用。比如資料庫連線池的實現,以及自定義分散式鎖的實現等。今天,就來介紹一下ThreadLocal的內部原理以及不規範的使用會導致的問題--記憶體洩漏。

本文的內容結構如下:

  1. ThreadLocal 的原理和原始碼
  2. ThreadLocal帶來的問題
  3. ThreadLocal的例項應用 - 基於Redis的分散式自旋鎖

一、ThreadLocal

  1. ThreadLocal核心方法

說到一個類,必然先檢視這個類的構造方式。ThreadLocal採用函數語言程式設計,定義一個靜態方法,傳入一個函式,從而構造ThreadLocal。例如:

ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() ->
{
    return new SimpleDateFormat("yyyy-MM-dd");
});

// 或者簡寫為 
ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"))

ThreadLocal中最核心的兩個方法,get 和 set。下面從原始碼角度分別對這兩個方法進行解析。

public T get() {
        Thread t = Thread.currentThread(); // 獲取當前執行緒
        ThreadLocalMap map = getMap(t); // ThreadLocalMap,ThreadLocal中的重要的內部類
        if (map != null) { 
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue(); // 否則直接設定初始值
    }

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

從原始碼中可以看出,get 和 set的方法都很簡單,其中涉及到的核心為 ThreadLocal的內部類ThreadLocalMap以及 核心方法getMapcreateMap

  1. ThreadLocalMap

和HashMap類似,內部也有一個節點類 Entry,該Entry繼承自弱引用。使用ThreadLocal作為key,要設定的值作為value。Entry的原始碼如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

在 Thread 中,有 ThreadLocalMap 型別的欄位。

Thread中的 ThreadLocalMap型別欄位

接下來,我們來看下 getMap 和 createMap 方法的實現,其實很簡單。

public class ThreadLocal<T> {
//...... 省略其他方法
  ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

		void createMap(Thread t, T firstValue) {
      // 傳入的key 為當前的ThreadLocal物件 this
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
}

二、ThreadLocal帶來的問題

由於 Entry繼承了弱引用,在不顯示的刪除 key 的時候,可能會造成 記憶體洩露。原因是,沒有顯示刪除key時,在發生GC的時候,弱引用會直接被回收掉,而對應的 value 卻還在記憶體中,這樣會造成記憶體洩漏。避免記憶體洩漏,需顯示呼叫 remove 方法。

弱引用(WeakReference)並不能使物件豁免垃圾收集,僅僅是提供一種訪問在弱引用狀態下物件的途徑。這就可以用來構建一種沒有特定約束的關係,比如,維護一種非強制性的對映關係,如果試圖獲取時物件還在,就使用它,否則重現例項化。它同樣是很多快取實現的選擇。

三、ThreadLocal的例項應用 - 基於Redis的分散式自旋鎖

接下來分享ThreadLocal的典型應用: 分散式自旋鎖。廢話不多說,直接上原始碼。

@Data
public class RedisLock {

    private RedisTemplate<String, String> redisTemplate;

    private int expireSeconds = 60; //鎖過期時間,預設1分鐘

    private int retryIntervalMs = 1000; //重試加鎖間隔時間(毫秒),預設1秒

    private int retryTimes = 3; //重試加鎖次數

    private final Logger log = LoggerFactory.getLogger(getClass());

    public RedisLock(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public RedisLock(RedisTemplate<String, String> redisTemplate, int expireSeconds) {
        this.redisTemplate = redisTemplate;
        this.expireSeconds = expireSeconds;
    }

    public RedisLock(RedisTemplate<String, String> redisTemplate, int expireSeconds, int retryTimes, int retryIntervalMs) {
        this.redisTemplate = redisTemplate;
        this.expireSeconds = expireSeconds;
        this.retryTimes = retryTimes;
        this.retryIntervalMs = retryIntervalMs;
    }

    /**
     * 加鎖
     * @param key
     * @return
     */
    public boolean acquire(String key) {
        int count = retryTimes;
        do{
            String lockValue = UUID.randomUUID().toString();
            //如果setnx ok,則表示redis中沒有針對當前key的鎖
            if(redisTemplate.opsForValue().setIfAbsent(key, lockValue, expireSeconds, TimeUnit.SECONDS)) {
                ThreadLocalContext.put(key, lockValue);
                if(log.isDebugEnabled()) {
                    log.debug("key [" + key + "] locked success!");
                }
                return true;
            }
            count--;
            if(count >= 0) {
                try {
                    Thread.sleep(retryIntervalMs);
                } catch (InterruptedException e) {
                    log.error(e.getMessage(), e);
                }
            }
        } while(count >= 0);

        return false;

    }

    /**
     * 釋放鎖
     * @param key
     */
    public void release(String key) {
        //只允許釋放當前執行緒自己加的鎖
        //某些特定情況下,可能導致加鎖成功後的業務處理耗時過長或者邏輯卡死,這時候鎖已經失效,可能被其它執行緒對該碼再次加鎖成功,
        //這時候便不能直接對其粗暴解鎖,還需要根據value值進行判斷當前鎖是否為自己加上去的,以防影響其它執行緒的處理過程。
        Object lockValue = ThreadLocalContext.get(key);
        try{
            if(lockValue!=null && lockValue.toString().equals(redisTemplate.opsForValue().get(key))) {
                redisTemplate.delete(key);
                if(log.isDebugEnabled()) {
                    log.debug("key [" + key + "] released success!");
                }
            }
        }finally {
            ThreadLocalContext.remove(key);
        }


    }


}
public class ThreadLocalContext {
    private static ThreadLocal<Map<String, Object>> CTX_HOLDER = new ThreadLocal();

    public ThreadLocalContext() {
    }

    public static void put(Map<String, Object> map) {
        Map<String, Object> ctxMap = (Map)CTX_HOLDER.get();
        if (ctxMap == null) {
            ctxMap = new HashMap();
            CTX_HOLDER.set(ctxMap);
        }

        ((Map)ctxMap).putAll(map);
    }

    public static void put(String key, Object value) {
        Map<String, Object> ctxMap = (Map)CTX_HOLDER.get();
        if (ctxMap == null) {
            ctxMap = new HashMap();
            CTX_HOLDER.set(ctxMap);
        }

        ((Map)ctxMap).put(key, value);
    }

    public static Map<String, Object> get() {
        return (Map)CTX_HOLDER.get();
    }

    public static boolean contains(String key) {
        Map<String, Object> ctxMap = (Map)CTX_HOLDER.get();
        return ctxMap != null ? ctxMap.containsKey(key) : false;
    }

    public static void clean() {
        CTX_HOLDER.set((Object)null);
    }

    public static void remove(String key) {
        ((Map)CTX_HOLDER.get()).remove(key);
    }

    public static Object get(String key) {
        Object value = null;
        Map<String, Object> ctxMap = (Map)CTX_HOLDER.get();
        if (ctxMap != null) {
            value = ctxMap.get(key);
        }

        return value;
    }

    static {
        CTX_HOLDER.set(new HashMap());
    }
}