ThreadLocal完全解析

ThreadLocal

Posted by Anriku on April 24, 2019

熟悉Android消息机制的话,对ThreadLocal这个类应该都不陌生。Android消息机制中的Looper就是通过ThrealLocal来实现为每个线程建立一个独立Looper的。

简单的Demo

public class ThreadLocalTest {

    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        // 给主线程设置ThreadLocal值
        threadLocal.set("I am in main thread");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 给子线程设置ThreadLocal值
                threadLocal.set("I am in sub thread");
                // 在子线程获取ThreadLocal值
                System.out.println(Thread.currentThread() + ":" + threadLocal.get());
            }
        });
        thread.start();
        // 等待子线程执行完
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 在主线程获取ThreadLocal值
        System.out.println(Thread.currentThread() + ":" + threadLocal.get());
    }
    
}

通过上面程序可以发现ThreadLocal在不同的线程设置和获取值是不会相互影响的。

ThreadLocal是如何实现不同线程独立存储的

其实每个线程都一个叫做threadLocalsThreaLocal.ThreadLocalMap类型成员属性。

深入源码可以发现ThreadLocalMap就是一个简易版的Hash表。这个Hash表keyWeakReference<ThreadLocal<?>>valueObject熟悉范性的同学都应该知道ThreadLocal<任意类型>都可以赋值给ThreadLocal<?>类型,相当于实现了范性的多态

ThreadLocal调用set方法的时候,最终都会以当前ThreadLocal的弱引用为key对应的set的值为value存入当前调用线程的threadLocals成员属性中去。

因为如果在不同线程同一ThreadLocal进行set方法调用,在set方法内获取的是不同Thread的threadLocals成员属性,因此达到了同一个ThreadLocal对象的在不同线程中进行值独立存储的要求。

下面是Thread的部分源码:

class Thread implements Runnable {
...
    ThreadLocal.ThreadLocalMap threadLocals = null;
...
}

最核心的类—ThreadLocalMap

前面已经说了,这就是一个简易版的在Hash表这里key为ThreadLocal的弱引用因此ThreadLocal是不会导致内存泄漏的。只要某个ThreadLocal的强引用没有了,GC时就ThreadLocalMap对应的ThreadLocal的弱引用也会被回收。

这个过程会产生不新鲜值也就是ThreadLocalMap中某个位置的弱引用的值为null,但其对应的value不为null。

不新鲜值在每次set方法调用的时候一定会进行相应的检测清除,get方法只有在遇到不新鲜值的时候才会进行相应的清除。对应的算法就不进行详解了。

这个简易版的Hash表以16作为初始容量,然后扩容因子2 / 3。以2倍进行扩容。因此其容量始终是2的幂次方。这样的好处和HashMap一样的可以通过与运算轻松的获取存储的值该放在Hash表的哪个位置。然后如果遇到Hash冲突,这里使用的是索引加一法进行冲突的解决。

下面是ThreadLocal.ThreadLocalMap的部分源码:

public class ThreadLocal<T> {
	...
	static class ThreadLocalMap {
		...
		private Entry[] table;
		
		static class Entry extends WeakReference<ThreadLocal<?>> {
    	/** The value associated with this ThreadLocal. */
    	Object value;

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

ThreadLocal的实现

public class ThreadLocal<T> {
...
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    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();
    }
  

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
...
}

通过上面部分源码应该很容易发现ThreadLocal就是借助于Thread的threadLocals整个Hash表进行不同线程值的独立存储的。

InheritableThreadLocal—可以继承的ThreadLocal

在Thread的源码中应该可以发现其实Thread有两个ThreadLocal.ThreadLocalMap类。

下面是部分源码:

class Thread implements Runnable {
...
    ThreadLocal.ThreadLocalMap threadLocals = null;

    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
...
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
    }
...

}

threadLocals就是使用普通的ThreadLocal类用到的;inheritableThreadLocals这个成员属性主要就是用于继承创建当前Thread的父Thread中的inheritableThreadLocals。

init方法是继承的具体实现。

下面是InheritableThreadLocal部分源码:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal就是ThreadLocal的子类,很简单就是使用线程的threadLocals改成了使用inheritableThreadLocals进行进行存储。

总结

总的来说,ThreadLocal.ThreadLocalMap就是一个通过索引加一法解决冲突的Hash表。

然后每个Thread中有两个ThreadLocal.ThreadLocalMap类型的成员属性:

  • threadLocals用于对ThreadLocal进行存储的。

  • inheritableThreadLocals用于对InheritableThreadLocal进行存储的。其中inheritableThreadLocals会在Thread的init方法调用的时候继承父Thread的inheritableThreadLocals。