Java学习笔记——并发之ThreadLocal

来源:互联网 发布:淘宝新店手机号码采集 编辑:程序博客网 时间:2024/06/10 16:29

    ThreadLocal是一种线程本地存储机制,可以为使用相同变量的每个不同线程都创建不同的存储。因此,如果你有5个线程都要使用变量x锁表示的对象,线程本地存储就会产生5个用于x的不同存储块。

1. ThreadLocal的用法

    ThreadLocal在是泛型类,可以使用set方法设置变量的值,使用get方法获取变量值,示例代码如下:

public class ThreadLocalTest {private final static class TimeCounter{private static ThreadLocal<Long> startTime = new ThreadLocal<Long>();private static void start(String name){startTime.set(System.currentTimeMillis());System.out.println(name + " start time is " + startTime.get());}private static void end(String name){if(startTime.get() == null){return;}System.out.println(name + " start time is " + startTime.get());System.out.println(name + " execute time is " + (System.currentTimeMillis() - startTime.get()));}}private static void doSomthing(String name, long sleepTime){TimeCounter.start(name);sleep(sleepTime);TimeCounter.end(name);}private static void sleep(long sleepTime) {try {Thread.sleep(sleepTime);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public static void main(String[] args) {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {doSomthing("t1", 10000);}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {sleep(5000);doSomthing("t2", 20000);}});t1.start();t2.start();}}
      上述代码是ThreadLocal的一种典型应用场景:在上下文中不方便直接传递参数(本代码中的startTIme)时,可以在一个方法中将参数保存到ThreadLocal中,再在另一方法中取出使用。本示例代码实现了一个简单的执行时间监测的功能,用于监测代码执行的性能,分别计算线程t1 和 线程t2的执行时间,执行结果如下:

t1 start time is 1512482050827t2 start time is 1512482055834t1 start time is 1512482050827t1 execute time is 10011t2 start time is 1512482055834t2 execute time is 20002
    可以看出变量startTIme在线程t1和线程t2中是独立的,两个线程的startTIme相互不影响。总结一下,ThreadLocal的使用方法:先初始化,然后使用set和get分别设置和获取值。伪代码如下:

ThreadLocal<T> x = new ThreadLocal<T>();....x.set(t);...x.get();

2.ThreadLocal源码解析

   在介绍THreadLocal的源码之前先说明一下:Thread类有一个类型为ThreadLocalMap的成员变量threadLocals,用于存储当前线程的本地变量。定义如下:

    /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;
  其中,ThreadLocalMap是ThreadLocal的内部类,是以数组存储数据的Key-value的Map(没有实现map接口)。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 getMap(Thread t) {        return t.threadLocals;    }    void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }
  set方法先从当前线程中获取threadLocals成员变量,如果为空则创建一个ThreadLocalMap并赋给当前线程的threadLocals。从上述代码可以看出,threadLocals的key为this指针,即当前调set方法的ThreadLocal变量。

  同样的,get方法也是先从当前线程中取得threadLocals成员变量,然后用this指针作为Key获取当前线程中的ThreadLocal变量.的值。

    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();    }
3. ThreadLocal的注意事项

 在使用线程池的场景下,由于线程执行完成后回归到线程池时,线程的threadLocals变量并不会被重置,因此当这个线程再次执行新任务时,原来的threadLocals的数据仍然存在,因此可能会影响当前任务执行的结果,示例代码如下;

public class TreadTest {public static void main(String[] args) {ThreadLocal<Integer> num = new ThreadLocal<Integer>();ExecutorService executor = Executors.newSingleThreadExecutor();Runnable t1 = new Runnable() {@Overridepublic void run() {System.out.println("thread 1: before set num is:" + num.get());num.set(1);System.out.println("thread 1: after set num is:" + num.get());}};Runnable t2 = new Runnable() {@Overridepublic void run() {System.out.println("thread 2: before set num is:" + num.get());num.set(2);System.out.println("thread 2: after set num is:" + num.get());}};executor.submit(t1);executor.submit(t2);}}
  执行结果如下所示;

thread 1: before set num is:nullthread 1: after set num is:1thread 2: before set num is:1thread 2: after set num is:2
 由于线程池里只有1个线程,虽然t1和t2是同时提交线程池的,但是必须等t1执行完成之后再执行t2,并且是线程池中的同一个线程执行的,因此执行t2时num的初始值变成了t1设置后的值,而不是null了,因此在使用ThreadLocal时,最好先初始化再使用。

 另外,子线程会继承父线程的ThreadLocal变量。