概述
翻译过来的大概意思是:ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问
(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。
总结:ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个独立的变量副本,提供了保持对象的方法和避免参数传递的复杂性。
实现原理
ThreadLocal可以看做是一个容器,容器里面存放着属于当前线程的变量。ThreadLocal类提供了四个对外开放的接口方法,这也是用户操作ThreadLocal类的基本方法:
- void set(Object value) 设置当前线程的线程局部变量的值。
- public Object get() 该方法返回当前线程所对应的线程局部变量。
- public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
- protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。
ThreadLocal内部如何为每一个线程维护变量副本呢? 其实在ThreadLocal类中有一个静态内部类ThreadLocalMap,用键值对的形式存储每一个线程的变量副本,其中key为当前ThreadLocal对象,而value对应线程的变量副本。每个线程可能存在多个ThreadLocal。
----------------------------------------------------------------------------------------------------------------------------------------------
源代码解析:
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { // 获取当前线程 Thread t = Thread.currentThread(); // 获取当前线程中对应的ThreadLocalMap(ThreadLocalMap为线程的一个属性) ThreadLocalMap map = getMap(t); if (map != null) { /* ThreadLocalMap的key为ThreadLocal对象,value为对应的变量副本 此处获取当前ThreadLocal对象对应的Entry */ ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { // 获取变量副本并返回 @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } // 如果ThreadLocalMap为空,则初始化 return setInitialValue(); } /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { /* 获取当前线程的ThreadLocalMap(若不存在则创建), 并将value放入ThreadLocalMap中 */ Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * {@code initialValue} method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map */ void createMap(Thread t, T firstValue) { /* 为当前线程创建一个ThreadLocalMap对象并赋值给threadLocals, 并存入第一个值(this表示当前ThreadLocal对象) */ t.threadLocals = new ThreadLocalMap(this, firstValue); } /** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ private T setInitialValue() { // 默认初始化为null T value = initialValue(); /* 获取当前线程的ThreadLocalMap(若不存在则创建), 并将value放入ThreadLocalMap中 */ Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
上述是在ThreadLocal类中的几个主要的方法,他们的核心都是对其内部类ThreadLocalMap进行操作,下面看一下该类的源代码:
/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. * * map中的每个节点Entry,其键key是ThreadLocal并且还是弱引用,这也导致了后续会产生内存泄漏问题的原因 */ static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } /** * The initial capacity -- MUST be a power of two. * * 初始化容量为16,以为对其扩充也必须是2的指数 */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. * * 真正用于存储线程的每个ThreadLocal的数组,将ThreadLocal和其对应的值包装为一个Entry。 */ private Entry[] table; // ...... 其他操作和Map类似
总之,为不同线程创建不同的ThreadLocalMap,以线程本身为区分点,每个线程之间其实没有任何的联系。说是存放了变量的副本,其实可以理解成为每个线程单独new了一个对象。
对象存放在哪里
在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
问:那么是不是说ThreadLocal的实例以及其值存放在栈上呢?
其实不是,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有)。而ThreadLocal的值其实也是被线程实例持有。它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见。
【注意:变量是保存在线程中的,而不是保存在ThreadLocal变量中】
真的只能被一个线程访问么
上面提到了ThreadLocal只对当前线程可见,是不是说ThreadLocal的值只能被一个线程访问呢?使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值。如下,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值。
private void testInheritableThreadLocal() {
final ThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("droidyue.com");
Thread t = new Thread() {
@Override
public void run() {
super.run();
Log.i(LOGTAG, "testInheritableThreadLocal = " + threadLocal.get());
}
};
t.start();
}
上面的代码输出的日志信息为:
testInheritableThreadLocal = droidyue.com
//Thread.java
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
//code goes here
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
上面代码就是在线程创建的时候,复制父线程的inheritableThreadLocals的数据。
使用场景
实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例副本。
承载一些线程相关的数据,避免在方法中来回传递参数。