ThreadLocal线程本地变量

一、什么是ThreadLocal

ThreadLocal并不是用来并发控制访问一个共同对象,而是为了给每个线程分配一个只属于该线程的变量,顾名思义它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突,实现线程间的数据隔离。从线程的角度看,就好像每一个线程都完全拥有该变量。

二、源码实现

set方法实现

public void set(T value) {
  Thread t = Thread.currentThread();//1
  ThreadLocalMap map = getMap(t);//2
  if (map != null)
      map.set(this, value);//4
  else
      createMap(t, value);//3
}
ThreadLocalMap getMap(Thread t) {
  return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
  t.threadLocals = new ThreadLocalMap(this, firstValue);
}

接下来我们按代码行逐行讲解:
1、获取当前线程,即t为正在执行ThreadLocal的set方法的那个线程
2、获取ThreadLocalMap,其中ThreadLocalMap为ThreadLocal的一个内部类,后面再详细解释,其中getMap()方法参数列表为当前线程地址
接下来,我们看一下getMap方法,这个方法只有一句话return t.threadLocals,显然,threadLocals类型为ThreadLocalMap,是Thread线程类的一个成员变量,我们打开Thread类查看该变量,发现其初始值为null

ThreadLocal.ThreadLocalMap threadLocals = null;

3、<createMap(t, value)>由于首次执行set方法时,threadLocals变量为null,所以执行代码3,即创建一个ThreadLocalMap,接下来我们看一下createMap()这个方法。参数列表包含两个,分别为当前线程和准备存储的值。实现代码只有一行

t.threadLocals = new ThreadLocalMap(this, firstValue);

不难发现,这个方法的实际作用其实就是为了当前线程的threadLocals变量做初始化,接下来我们看一下初始化细节new ThreadLocalMap(this, firstValue)。参数列表包含两个,分别为当前ThreadLocal对象,准备存储的值。ThreadLocalMap初始化代码为

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
	table = new Entry[INITIAL_CAPACITY];
	int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
	table[i] = new Entry(firstKey, firstValue);
	size = 1;
	setThreshold(INITIAL_CAPACITY);
}

ThreadLocalMap其实本质上就是一个弱化版的HashMap,其中维持了一个Entry数组,Entry数组的每一维分别为一个链表,其中Entry数组初始长度为INITIAL_CAPACITY=16,接下来看这句代码

firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

很显然,是根据传入的ThreadLocal对象的HashCode对数组长度除留取余,i则是firstValue对应的需要存储的Entry数组下标
4、<map.set(this, value)>当map不为null时,将值存入map,具体实现代码为

private void set(ThreadLocal<?> key, Object value) {
	Entry[] tab = table;
	int len = tab.length;
	int i = key.threadLocalHashCode & (len-1);
	for (Entry e = tab[i];
	e != null;
	e = tab[i = nextIndex(i, len)]) {
		ThreadLocal<?> k = e.get();
		if (k == key) {
			e.value = value;
			return;
		}
		if (k == null) {
			replaceStaleEntry(key, value, i);
			return;
		}
	}
	tab[i] = new Entry(key, value);
	int sz = ++size;
	if (!cleanSomeSlots(i, sz) && sz >= threshold)
	rehash();
}

存储过程类似HashMap存储

get方法实现

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,若map不为null,则调用ThreadLocalMap的getEntry方法,以ThreadLocal对象为key,获取对应的Entry对象,并获取值

remove方法实现

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

根据ThreadLocal对象,移除对应值

三、内存泄漏问题

由于每个thread中都持有一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。
所以,只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值