併發編程-線程局部變量ThreadLocal

ThreadLocal原理及其實際應用

前言

java猿在面試中,常常會被問到1個問題:
java實現同步有哪幾種方式?html

你們通常都會回答使用synchronized, 那麼還有其餘方式嗎? 答案是確定的, 另一種方式也就是本文要說的ThreadLocal。java

ThreadLocal介紹

ThreadLocal, 看名字也能猜到, "線程本地", "線程本地變量"。 咱們看下官方的一段話:web

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

粗略地翻譯一下:
ThreadLocal這個類提供線程本地的變量。這些變量與通常正常的變量不一樣,它們在每一個線程中都是獨立的。ThreadLocal實例最典型的運用就是在類的私有靜態變量中定義,並與線程關聯。面試

什麼意思呢? 下面咱們經過1個實例來講明一下:安全

jdk中的SimpleDateFormat類不是一個線程安全的類,在多線程中使用會出現問題,咱們會經過線程同步來處理:數據結構

  1. 使用synchronized多線程

    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    
    public synchronized static String formatDate(Date date) {
        return sdf.format(date);
    }
  2. 使用ThreadLocalide

    private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue()
        {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    
    public String formatIt(Date date)
    {
        return formatter.get().format(date);
    }

這兩種方式是同樣的,只不過一種用了synchronized,另一種用了ThreadLocal。this

synchronized和ThreadLocal的區別

使用synchronized的話,表示當前只有1個線程才能訪問方法,其餘線程都會被阻塞。當訪問的線程也阻塞的時候,其餘全部訪問該方法的線程所有都會阻塞,這個方法至關地 "耗時"。
使用ThreadLocal的話,表示每一個線程的本地變量中都有SimpleDateFormat這個實例的引用,也就是各個線程之間徹底沒有關係,也就不存在同步問題了。spa

綜合來講:使用synchronized是一種 "以時間換空間"的概念, 而使用ThreadLocal則是 "以空間換時間"的概念。

ThreadLocal原理分析

咱們先看下ThreadLocal的類結構:

咱們看到ThreadLocal內部有個ThreadLocalMap內部類,ThreadLocalMap內部有個Entry內部類。

先介紹一下ThreadLocalMap和ThreadLocalMap.Entry內部類:
ThreadLocalMap其實也就是一個爲ThreadLocal服務的自定義的hashmap類。
Entry是一個繼承WeakReference類的類,也就是ThreadLocalMap這個hash map中的每一項,而且Entry中的key基本上都是ThreadLocal。

再下來咱們看下Thread線程類:
Thread線程類內部有個ThreadLocal.ThreadLocalMap類型的屬性:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

下面重點來看ThreadLocal類的源碼:

public T get() {
    // 獲得當前線程
    Thread t = Thread.currentThread();
    // 拿到當前線程的ThreadLocalMap對象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 找到該ThreadLocal對應的Entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 當前線程沒有ThreadLocalMap對象的話,那麼就初始化ThreadLocalMap
    return setInitialValue();
}

private T setInitialValue() {
    // 初始化ThreadLocalMap,默認返回null,可由子類擴展
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 實例化ThreadLocalMap以後,將初始值丟入到Map中
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

public void set(T value) {
    // set邏輯:找到當前線程的ThreadLocalMap,找到的話,設置對應的值,不然建立ThreadLocalMap
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

註釋已經寫了,讀者有不明白的能夠本身看看源碼。

ThreadLocal的應用

ThreadLocal應用普遍,下面介紹下在SpringMVC中的應用。

RequestContextHolder內部結構

RequestContextHolder:該類會暴露與線程綁定的RequestAttributes對象,什麼意思呢? 就是說web請求過來的數據能夠跟線程綁定, 用戶A,用戶B分別請求過來,可使用RequestContextHolder獲得各個請求的數據。

RequestContextHolder數據結構:

具體這兩個holder:

private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
        new NamedThreadLocal<RequestAttributes>("Request attributes");

private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
        new NamedInheritableThreadLocal<RequestAttributes>("Request context");

這裏的NamedThreadLocal只是1個帶name屬性的ThreadLocal:

public class NamedThreadLocal<T> extends ThreadLocal<T> {

    private final String name;

    public NamedThreadLocal(String name) {
        Assert.hasText(name, "Name must not be empty");
        this.name = name;
    }

    @Override
    public String toString() {
        return this.name;
    }

}

繼續看下RequestContextHolder的getRequestAttributes方法,其中接口RequestAttributes是對請求request的封裝:

public static RequestAttributes getRequestAttributes() {
    // 直接從ThreadLocalContext拿當前線程的RequestAttributes
    RequestAttributes attributes = requestAttributesHolder.get();
    if (attributes == null) {
        attributes = inheritableRequestAttributesHolder.get();
    }
    return attributes;
}

咱們看到,這裏直接使用了ThreadLocal的get方法獲得了RequestAttributes。
當須要獲得Request的時候執行:

ServletRequestAttributes requestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();

RequestContextHolder的初始化

如下代碼在FrameworkServlet代碼中:


總結

本文介紹了ThreadLocal的原理以及ThreadLocal在SpringMVC中的應用。我的感受ThreadLocal應用場景更可能是共享一個變量,可是該變量又不是線程安全的,而不是線程同步。好比RequestContextHolder、LocaleContextHolder、SimpleDateFormat等的共享應用。

相關文章
相關標籤/搜索