天天看點

ThreadLocal原理和項目中如何使用

本教程分如下三個部分

1. 項目中如何使用Threadlocal

2. Threadlocal和Thread關系以及Threadlocal源碼分析

3. Threadlocal的DEMO練習(提供github和碼雲下載下傳源代碼)

首先上幹貨。講講我司項目中如何使用ThreadLocal。

ThreadLocal原理和項目中如何使用

這是一個登入會話保持的靜态類,用來儲存目前線程的登入資訊。

使用AssertionContent原因:

由于通過ServletRequest request轉換成

可以通過

HttpSession session = httpRequest.getSession( false );

得到session。但是,并不是每個業務類或者方法都能得到目前的httpRequest。是以,就可以使用ThreadLocal在任何目前線程的任何業務類中,得到session。

在登入的filter中,可以通過

session.setAttribute( BrowserSession.ASSERTION, bs );

将目前BrowserSession儲存到會話中。

BrowserSession bs = new BrowserSession();
        bs.setUid(userId);
        bs.setUname(name);
        ...
           

BrowserSession 是目前使用者登入的一些個人資訊。是業務自定義的實體類。

下一次再通路的時候,可以直接從session中得到使用者所有資訊。

在業務相關的filter中,

ThreadLocal原理和項目中如何使用

其中set方法如下:

public void setAttribute( String name, Object value ) {
        attributes.put( name, value );
    }
           

以上set方法就是将session會話儲存到AssertionContext

ThreadLocal原理和項目中如何使用

注意:此處是用一個AssertionContext中的一個Threadlocal變量儲存了session。

是以在取session的時候,使用如下圖所示方法:

ThreadLocal原理和項目中如何使用

其中AssertionContex.getContext()方法大緻如下:

public static AssertionContext getContext() {
        AssertionContext context = contextHolder.get();
        ...
        return context;
    }
           

其實也可以将以上步驟放到一個filter中,怎麼順手怎麼使用。

接下來,就是在任何你想要使用session的地方,取得session,比如在業務service中:

ThreadLocal原理和項目中如何使用

其中checkLogin方法就是通過上面說到的先得到AssertionContext 中的Threadlocal再得到session來獲得。

以上是講解如何在業務中使用Threadlocal,下面結合源碼介紹下原理:

Threadlocal設計兩個java類:Thread和Threadlocal。

首先講解下他們之間的關系

ThreadLocal原理和項目中如何使用

可以看到,Thread有個成員變量ThreadLocal.ThreadLocalMap,而ThreadLocalMap是Threadlocal的内部靜态類。ThreadLocalMap中存的值的key為this,即目前Threadlocal類的引用,這樣,每次從同一個Threadlocal和同一個Thread中得到的值就唯一确定了。

看一下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);
    }
           

首先得到目前線程,然後得到目前線程的ThreadlocalMap。由于隻要線程确定,是以map就确定。

getMap方法如下:

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

直接放回目前線程的成員變量。

可以看到第一次得到的map肯定為null,我們接下來看createMap方法:

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

直接new了一個ThreadLocalMap,其中key為目前Threadlocal的引用。

如果map的值不為null則直接把值set到map中去。

看完set方法接着看get方法:

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

首先,還是得到目前線程,得到目前線程的map,如果map不為空,則:

得到key為this的值,然後傳回該值。如果map為空則調用setInitialValue方法

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
           

繼續判斷map是否為空(這裡再進行判斷,我認為可能是在多個線程并發執行的情況下,如果執行到方法的入口處,其他的線程又new了一個ThreadlocalMap,辣麼此處就不需要再new了),這裡的initialValue()方法直接放回的是一個null。

到此,Threadlocal的set和get方法和原理都介紹完了。需要注意的是,由于ThreadlocalMap中的key是this引用,也就是說,this引用如果指向不同的對象,辣麼通過get方法得到的值就不是希望得到的那個值。是以,要想每次得到的值都是正确的,必須使this指針指向的對象唯一,這就解釋了為什麼Threadlocal都使用靜态變量來儲存。

為了更好的了解Threadlocal的原理,下面有幾個Threadlocal的demo練習,非常簡單。大家可以clone下來或者fork下來試試。

git位址:

https://github.com/tengqingya/ThreadLocalPractice

碼雲位址:

https://git.oschina.net/tengqingya/ThreadLocalPractice

請尊重作者和版權,轉載請标明出處。

聯系作者:qq475804848,滕慶亞