天天看点

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

    }

}

后记:

很多人问我,在父类中既然可以获取到上下文那直接传给子类方法就行了啊。注意前提说的是父类只负责提供一个容器,具体的实现由不同的部门和项目组编写