天天看點

Java中ThreadLocal應用總結(2)

一、需求介紹

由于項目需要在同一程序不同方法内擷取到上下文内容,而在同一線程父方法可以擷取到上下文内容,子方法由不同的項目組提供和開發,然後以jar包的方式打包,這時候問題就出現了,父方法的上下文内容如何可以提供給子方法,并在子方法中擷取到對應線程的上下文内容?

注:每個線程的上下文内容是不同的,父類隻提供內建容器,具體方法的實作由不同的項目組和部門編寫

二、解決思路

應用Java 提供的ThreadLocal

首先檢視下TheadLocal提供的方法

T get()

          傳回此線程局部變量的目前線程副本中的值。

protected  T initialValue()

          傳回此線程局部變量的目前線程的初始值。

void remove()

          移除此線程局部變量的值。

void set(T value)

          将此線程局部變量的目前線程副本中的值設定為指定值。

2.1、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();

}

說明:

1、首先擷取目前線程

2、擷取目前線程的ThreadLocalMap即threadLocals

getMap(Thread t)源碼為:

ThreadLocalMap getMap(Thread t) {

    return t.threadLocals;

}

3、如果目前線程存在threadLocals則根據目前的ThreadLocal擷取threadLocals的值.(threadLocals其實就是一Map)

4、如果目前線程的ThreadLocalMap為null,則執行方法setInitialValue()

可以看的出是擷取Thread的threadLocals

Thread中源碼為如下:

ThreadLocal.ThreadLocalMap threadLocals = null;

2.2、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);

}

源碼說明:

1、首先擷取目前線程

2、擷取目前線程ThreadLocalMap

3、如果ThreadLocalMap不為null set(ThreadLocal,value)

4、如果為null createMap(Thread,value);

備注:這裡可以看出不為null時,set的内容為key:ThreadLocal,value:value,也就是說每個線程都有一個ThreadLocalMap這個map記憶體放得内容為ThreadLocal,value

接下來在看下createMap源碼

void createMap(Thread t, T firstValue) {

    t.threadLocals = new ThreadLocalMap(this, firstValue);

}

備注:該方法為目前線程的内部變量threadLocals進行了指派操作,而ThreadLocalMap為ThreadLocal的内部類。new ThreadLocalMap(this, firstValue) 内部進行的操作也是為目前線程的threadLocals變量進行了set操作,key為目前的ThreadLocal,value為傳入的firstValue

2.3、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;

}

該方法傳回null,并會建立目前線程的threadLocals

綜上:應用ThreadLocal線上程中進行上下文同步時,要保持一緻的ThreadLocal。當多線程應用線程池時,要保證線程池中相同的線程每次拿到得ThreadLocal要不相同,否則會出現問題。

回到問題的開始,解決該問題的方案為

1、建立一個單例類(ex:ThreadLocalTest)保證線程上下文拿到得ThreadLocal都一緻

2、父類提供給子類可以擷取單例類ThreadLocalTest的方法。(要是子類分别由不同的部門、項目組以jar的方式提供時,負責父類服務的可以給項目組提供一個通用的jar檔案)

3、在調用父類多線程入口時擷取ThreadLocalTest單例,set(上下文内容)

4、子類在方法内擷取ThreadLocalTest單例調用get()方法擷取目前線程的value即為上下文内容

5、父類調用子方法後執行ThreadLocalTest.remove()操作

public class ThreadLocalTest<T> {

    private static ThreadLocal local = new ThreadLocal();

    private static ThreadLocalTest threadlocaltest = null;

    public synchronized ThreadLocalTest getInitialize(){

        if(threadlocaltest == null){

            threadlocaltest = new ThreadLocalTest<T>();

        }

        return threadlocaltest;

    }

    public T get(){

        return (T) local.get();

    }

    public void set(T t){

        local.set(t);

    }

    public void remove(){

        local.remove();

    }

}

後記:

很多人問我,在父類中既然可以擷取到上下文那直接傳給子類方法就行了啊。注意前提說的是父類隻負責提供一個容器,具體的實作由不同的部門和項目組編寫