邹冬冬

导航

ThreadLocal详解

一、对ThreadLocal(线程局部变量)的理解

ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。
使用这个工具类可以很简洁地编写出优美的多线程程序。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
 
二、ThreadLocal的常用方法
1、设置当前线程中变量的副本

public void set(T value) {
  Thread t = Thread.currentThread();//获得当前的线程
  ThreadLocalMap map = getMap(t);//获得当前线程的Map(用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本)
  if (map != null)          
    map.set(this, value);//如果map存在的话,就将当前ThreadLocal对象作为key,需要保存的变量作为value,存放在map    
  else
    createMap(t, value);//如果map不存在,就初始化map
}


2、返回当前线程对应的线程局部变量

public T get() {
  Thread t = Thread.currentThread();//获得当前线程
  ThreadLocalMap map = getMap(t);//获得当前线程的Map
  if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);//获得key(当前ThreadLocal对象)对应的entry
    if (e != null) {
      @SuppressWarnings("unchecked")
      T result = (T)e.value;//读取entry对应的value
      return result;
    }
  }
  return setInitialValue();
}

 

3、返回当前线程Map中key=当前ThreadLocal对象对应value的初始值

protected T initialValue() {
  return null;
}

在线程通过get()方法获得变量的时候initialValue()会被第一次调用

如果在get()方法前,先调用了set()方法,那么initialValue()不会被调用

 

4、删除当前线程Map中key=当前ThreadLocal对象对应value的初始值

public void remove() {
  ThreadLocalMap m = getMap(Thread.currentThread());
  if (m != null)
    m.remove(this);
  }
}

 

5、(特别注意)ThreadLocalMap中Entry的key是软引用

static class Entry extends WeakReference<ThreadLocal<?>> {
  /** The value associated with this ThreadLocal. */
  Object value;

  Entry(ThreadLocal<?> k, Object v) {
    super(k);
    value = v;
  }
}

软引用

WeakReference是Java语言规范中为了区别直接的对象引用(程序中通过构造函数声明出来的对象引用)而定义的另外一种引用关系。
WeakReference标志性的特点是:reference实例不会影响到被应用对象的GC回收行为(即只要对象被除WeakReference对象之外所有的对象解除引用后,该对象便可以被GC回收),只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null

理解 : ThreadLocalMap中的key是弱引用,不会影响到threadLocal的GC行为。如果是强引用的话,在线程运行过程中,我们不再使用threadLocal了,将threadLocal置为null,但threadLocal在线程的ThreadLocalMap里还有引用,导致其无法被GC回收(当然,可以等到线程运行结束后,整个Map都会被回收,但很多线程要运行很久,如果等到线程结束,便会一直占着内存空间)。而key声明为WeakReference,threadLocal置为null后,线程的threadLocalMap就不算强引用了,threadLocal就可以被GC回收了。map的后续操作中,也会逐渐把对应的"stale entry"清理出去,避免内存泄漏。

  所以,我们在使用完ThreadLocal变量时,尽量用threadLocal.remove()来清除,避免threadLocal=null的操作。前者remove()会同时清除掉线程threadLocalMap里的entry,算是彻底清除;而后者虽然释放掉了threadLocal,但线种threadLocalMap里还有其"stale entry",后续还需要处理。

 

三、应用场景

ThreadLocal的典型用途是提供上下文信息,比如在一个Web服务器中,一个线程执行用户的请求,在执行过程中,很多代码都会访问一些共同的信息,比如请求信息、用户身份信息、数据库连接、当前事务等,它们是线程执行过程中的全局信息,如果作为参数在不同代码间传递,代码会很啰嗦,这时,使用ThreadLocal就很方便,所以它被用于各种框架如Spring中,我们看个简单的示例:

public class RequestContext {
  public static class Request { //...
  };

  private static ThreadLocal<String> localUserId = new ThreadLocal<>();
  private static ThreadLocal<Request> localRequest = new ThreadLocal<>();

  public static String getCurrentUserId() {
    return localUserId.get();
  }

  public static void setCurrentUserId(String userId) {
    localUserId.set(userId);
  }

  public static Request getCurrentRequest() {
    return localRequest.get();
  }

  public static void setCurrentRequest(Request request) {
    localRequest.set(request);
  }

}

 

 

 



posted on 2017-08-25 13:16  邹冬冬  阅读(177)  评论(0编辑  收藏  举报