在单线程的程序里,可以通过static变量或者singleton对象的方式定义全局的、共享的变量。 在多线程的程序里,如果想定义在一个线程内部共享的变量,可以考虑使用ThreadLocal

ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread

一般来说,ThreadLocal对象会被声明成类的私有静态成员,比如下面这个例子来自ThreadLocal的javadoc

 public class ThreadId {
     // Atomic integer containing the next thread ID to be assigned
     private static final AtomicInteger nextId = new AtomicInteger(0);

     // Thread local variable containing each thread's ID
     private static final ThreadLocal<Integer> threadId =
         new ThreadLocal<Integer>() {
             @Override protected Integer initialValue() {
                 // 让不同的线程有不同的初始值
                 return nextId.getAndIncrement();
         }
     };

     // 不同的线程调用threadId.get()会返回不同的值,每个线程都有其各种的拷贝
     public static int get() {
         return threadId.get();
     }
 }
ThreadLocal的实现

每个线程对应的Thread对象里存在着一个map(所以不同线程的map相互隔离,没有竞争),

// 摘自Thread类的定义
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap的实现是一个hash表,定义如下,

static class ThreadLocalMap {
	static class Entry extends WeakReference<ThreadLocal<?>> {
		/** The value associated with this ThreadLocal. */
		// ThreadLocal对象关联的对象
		Object value;
		
		Entry(ThreadLocal<?> k, Object v) {
			super(k);
			value = v;
		}		
	}
	
	private Entry[] table;
}      

map中的key由ThreadLocal对象们担当,value则是ThreadLocal对象所关联的对象。

下面是ThreadLocal::get()的伪代码(真实的实现考虑了threadLocals的lazy initialization),

public class ThreadLocal<T> {
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap.Entry e = t.threadLocals.get(this);
        if (e == null) {
            t.threadLocals.set(this, this.initialValue());
        }
        return (T)e.value;
    }
} 
ThreadLocal和内存泄漏

Thread类对ThreadLocal对象只存在弱引用(通过Thread->ThreadLocalMap->Entry->(弱)ThreadLocal)。 当某个ThreadLocal对象被垃圾回收后(比如在ThreadId的例子中令threadId = null,对应的ThreadLocal对象就会稍后被垃圾回收), 会清除对其关联的对象的引用(详见ThreadLocalMap::expungeStaleEntry的实现)。 但是,这个动作不是实时的。 只有当ThreadLocalMap访问到某个Entry且发现相应的ThreadLocal变成null之后,才会解除对这个ThreadLocal所关联的对象的引用。

ThreadLocalMap的类定义有如下的注释,

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.

意思是ThreadLocalMap仅仅保证在内部空间发生重分配的情况下会肯定清理“过时的”Entry,其它情况下不做保证。

这就导致了内存泄漏的风险。 如果ThreadLocal所关联的对象是个大对象或者属于一个大的object map的一部分,那么即使ThreadLocal对象已经被垃圾回收, 其关联对象仍有可能长时间不能被回收。 ThreadLocalMap的内部数组长度是INITIAL_CAPACITY = 16,一般来说一个线程的ThreadLocal数目不会增长地太大从而导致内部数组发生重分配。 如果再加上线程长时间运行,那么ThreadLocal所关联的对象被释放的可能性就会变小。这属于关联对象的内存泄漏。

还有一种是ThreadLocal对象的内存泄漏。 之前提到,ThreadLocal对象一般会被声明成类的私有静态成员,

private static final ThreadLocal<Integer> threadId = ...

这个静态成员一般不太可能会在某个时间点被设为null。 Thread只有在终止时才会解除对ThreadLocal对象的引用(在exit()方法里令threadLocals = null;)。 所以一般来说,只要线程不终止,ThreadLocal对象一般会一直驻留内存。

这篇Tomcat wiki提供了很多和ThreadLocal相关的内存泄漏的例子。 Tomcat使用了线程池,所以一个Thread的生命周期很长。 另外Tomcat会为每个web app创建了相应的class loader。 这样线程池的Thread就通过ThreadLocal和一个web app的所有class建立了间接的引用关系 (Thread->…->ThreadLocal->SomeClass->ClassLoaderA->这个web app相关的类)。 如果重启web app,Tomcat会新建一个class loader来加载这个web app的类。 而这个web app之前的class loader和所有类定义还通过线程池里的Thread一直驻留在内存里,这就导致了内存泄漏。

Java的ThreadPoolExecutor提供了beforeExecute(),通过它可以清理一个Thread在执行之前的task时所遗留的ThreadLocal对象。

Method invoked prior to executing the given Runnable in the given thread. This method is invoked by thread t that will execute task r, and may be used to re-initialize ThreadLocals, or to perform logging.

只是Thread类的threadLocals成员并不是public成员,需要通过反射才能访问它。