java.lang.ThreadLocal实现原理分析

一、简介

ThreadLocal隶属java.lang包,表示线程私有变量,也可叫做线程本地变量。它为单个线程单独创立了一个副本,每个线程只可访问属于自己的变量,不可访问和修改别的线程所属的变量。
ThreadLocal属于一个泛型类,泛型参数为变量的类型,可以通过重写initialValue方法来实现对该变量初始值的设置。
ThreadLocal提供了以下API实现对变量的控制:

public T get(); //返回该变量
public void set(T value); //设置该变量
public void remove(); //移除该变量

例如下面的代码:

public class Test {

	private static ThreadLocal<Integer> tl = new ThreadLocal<Integer>() {
		//重写设置初始值的方法
		@Override
		protected Integer initialValue() {
	        return 0;
	    }
	};

	public static void main(String[] args) {
		System.out.println(tl.get());
		tl.set(tl.get() + 1);
		System.out.println(tl.get());
		
		Thread t = new Thread() {
			public void run() {
				System.out.println(tl.get());
			}
		};
		
		t.start();
	}
}

输出为:

0
1
0

看到这里想必大家理解了ThreadLocal的使用方法及其作用了吧。

二、源码分析

ThreadLocal的主要方法有:

public T get(); //返回该变量
public void set(T value); //设置该变量
public void remove(); //移除该变量
protected T initialValue(); //返回变量的初始值
(1)initialValue方法

initialValue方法的作用是为变量设置初始值,该方法的默认实现是:

protected T initialValue() {
	return null;
}

如果想要为该变量设置一个初始值,只需重写该方法即可,例如:

@Override
protected String initialValue() {
	return "hello world";
}

ThreadLocal对象中很多方法的实现会调用该方法来设置初始值。

(2)get方法分析

想要了解ThreadLocal运作机制,我们可以从get方法作为切入点分析。

public T get() {
	Thread t = Thread.currentThread();
	//获取当前线程对象t对应的ThreadLocalMap
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		//以当前ThreadLocal对象为键查找值
		ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
			@SuppressWarnings("unchecked")
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}

首先是获取当前运行线程对象,然后根据该线程对象找到对应的ThreadLocalMapThreadLocalMapThreadLocal的一个default访问权限内部类,其作用我们稍后分析)。如果找到了该线程对应的ThreadLocalMap,则通过当前ThreadLocal对象作为键查找Map中对应的Entry(键值对)对象,如果查找结果不为null,则返回Entry对象的value。否则调用setInitialValue方法将当前ThreadLocal对象(this)和变量作为键值对存入ThreadLocalMap并返回变量。

getMap方法:

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

该方法很简单,即返回Thread对象的成员变量threadLocals。
查看java.lang.Thread对象源码:

ThreadLocal.ThreadLocalMap threadLocals = null;

相信很多人看到这里已经大致明白:每个Thread对象会持有一个ThreadLocalMap,当我们创建一个ThreadLocal对象时,该ThreadLocal对象本身作为键,变量作为值存入该ThreadLocalMap中,来实现线程的私有变量。

下面再来看setInitialValue方法的实现:

private T setInitialValue() {
	T value = initialValue();
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
	return value;
}

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

该方法作用是将ThreadLocal对象存入当前线程对象的ThreadLocalMap中,最后返回变量初始值。
该方法首先会调用initialValue方法初始化变量,然后获取当前Thread对象所属的ThreadLocalMap(即成员变量threadLocals),如果Thread对象成员变量threadLocals不为空,则将当前ThreadLocal对象及其变量存入Map,否则会调用createMap方法为当前Thread对象新建一个ThreadLocalMap并存入当前ThreadLocal对象及其变量。

(3)ThreadLocal内部类:ThreadLocalMap

ThreadLocalMap负责存储ThreadLocal及其变量,即ThreadLocal对象本身作为键,ThreadLocal存储的变量作为值。每个Thread对象在声明一个ThreadLocal后会持有一个ThreadLocalMap的引用,来实现ThreadLocal的功能。

ThreadLocalMap持有一个内部类Entry,类似于HashMap.Node类,负责保存键值对。

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

Entry继承了WeakReference类,使Entry的键为弱引用。

看到这里很多人或许有这样一个疑问:为什么Entry要继承WeakReference?
既然将ThreadLocal声明为弱引用,那么自然会联想到和GC有关。

如果不声明为弱引用,以最上面Test类的代码为例,当我们将上述ThreadLocal类型的静态变量tl设置为null时,Thread对象成员变量threadLocals依然保留有一个ThreadLocalMap,该Map中持有保存该ThreadLocalEntry,在这个线程运行期间无法GC,从而引发内存泄漏。所以,Entry的键要声明为弱引用。
java.lang.ThreadLocal实现原理分析_第1张图片
当我们弃用一个ThreadLocal对象时,最好通过调用其remove方法而不是直接设为null

当向ThreadLocalMap添加一个ThreadLocal时,会获取ThreadLocal对象的threadLocalHashCode获取HashCode添加至Map中而不是调用hashCode()方法。
ThreadLocalMap持有一个Entry数组,采用线性探测法处理哈希冲突,而不是HashMap的拉链法。
为了尽量避免哈希冲突,ThreadLocal提供了hashCode的计算代码:

private static AtomicInteger nextHashCode = new AtomicInteger();

/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;

/**
* Returns the next hash code.
*/
private static int nextHashCode() {
	return nextHashCode.getAndAdd(HASH_INCREMENT);
}

每当新建一个ThreadLocal对象,会调用nextHashCode获取自己的hashCode
关于魔数0x61c88647,有兴趣的可以百度,这里不再赘述。

(4)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);
}

get方法类似,set方法首先会获取当前运行的Thread对象并通过该对象获取对应的ThreadLocalMap,如果map为空,则为当前Thread对象新建一个ThreadLocalMap,否则直接将value放入map中。

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

同样,获取当前Thread对应的ThreadLocalMap,然后调用ThreadLocalMapremove方法移除ThreadLocal对象,无需通过弱引用机制对该ThreadLocal对象进行GC。

如果本文有错误或对文章有疑问,欢迎在评论区评论。

你可能感兴趣的:(Java)