ThreadLocal 內部實現和應用場景(慎用,可能有內存泄露)

不少人都知道java中有ThreadLocal這個類,可是知道ThreadLocal這個類具體有什麼做用,而後適用什麼樣的業務場景仍是不多的。今天我就嘗試以本身的理解,來說解下ThreadLocal類的內部實現和應用場景,若是有什麼不對之處,還望你們指正。java

首先明確一個概念,那就是ThreadLocal並非用來併發控制訪問一個共同對象,而是爲了給每一個線程分配一個只屬於該線程的對象(這麼粗暴的解釋可能還不太準確),更準確的說是爲了實現線程間的數據隔離。而ThreadLocal應用場景更可能是想共享一個變量,可是該變量又不是線程安全的,那麼能夠用ThreadLocal維護一個線程一個實例。有時候ThreadLocal也能夠用來避免一些參數傳遞,經過ThreadLocal來訪問對象。web

首先咱們先看下ThreadLocal(jdk1.7)內部的實現:
ThreadLocal get方法緩存

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

ThreadLocal set方法安全

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類中最經常使用的兩個方法。併發

get方法ide

代碼很容易理解,首先咱們經過Thread.currentThread獲得當前線程,而後獲取當前線程的threadLocals變量,這個變量就是ThreadLocalMap類型的。而後根據當前的ThreadLocal實例做爲key,獲取到Entry對象。性能

set方法this

代碼一樣很容易理解。一樣根據Thread.currentThread獲得當前線程,若是當前線程存在threadLocals這個變量不爲空,那麼根據當前的ThreadLocal實例做爲key尋找在map中位置,而後用新的value值來替換舊值。線程

在ThreadLocal這個類中比較引人注目的應該是ThreadLocal->ThreadLocalMap->Entry這個類。這個類繼承自WeakReference。關於弱引用的知識,之後我會抽時間寫篇文章來介紹下。code

最近在咱們的web項目中servlet須要頻繁建立SimpleDateFormat這個對象,進行日期格式化。由於建立這個對象自己很費時的,並且咱們也知道SimpleDateFormat自己不是線程安全的,也不能緩存一個共享的SimpleDateFormat實例,爲此咱們想到使用ThreadLocal來給每一個線程緩存一個SimpleDateFormat實例,提升性能。同時由於每一個Servlet會用到不一樣pattern的時間格式化類,因此咱們對應每一種pattern生成了一個ThreadLocal實例。

DateFormatFactory

public class DateFormatFactory {

private static final Map<DatePattern, ThreadLocal<DateFormat>> pattern2ThreadLocal;

static {
    DatePattern[] patterns = DatePattern.values();
    int len = patterns.length;
    pattern2ThreadLocal = new HashMap<DatePattern, ThreadLocal<DateFormat>>(len);

    for (int i = 0; i < len; i++) {
        DatePattern datePattern = patterns[i];
        final String pattern = datePattern.pattern;

        pattern2ThreadLocal.put(datePattern, new ThreadLocal<DateFormat>() {
            @Override
            protected DateFormat initialValue() {
                return new SimpleDateFormat(pattern);
            }
        });
    }
}

//獲取DateFormat
public static DateFormat getDateFormat(DatePattern pattern) {
    ThreadLocal<DateFormat> threadDateFormat = pattern2ThreadLocal.get(pattern);
    //不須要判斷threadDateFormat是否爲空
    return threadDateFormat.get(); 
}

}

DatePattern 枚舉類

public enum DatePattern {

TimePattern("yyyy-MM-dd HH:mm:ss"),

DatePattern("yyyy-MM-dd"),

public String pattern;

private DatePattern(String pattern) {
    this.pattern = pattern;
}

}

這樣咱們就能夠每次調用DateFormatFactory.getDateFormat獲取到對應的時間格式化類了。以前咱們提到使用ThreadLocal同時能夠避免參數傳遞。假如咱們這個Servlet要調用到其餘類的方法,並且方法內須要使用時間格式化類。按照正常狀況下咱們把該時間格式化類做爲參數進行傳遞,但若是有了ThreadLocal這個類,咱們能夠不須要做爲參數傳遞了,能夠在方法類經過ThreadLocal獲得時間格式化類。固然代碼的通用性就差了。

相關文章
相關標籤/搜索