threadlocal
threadlocal为每一个线程单独创建一个变量副本,各个线程之间互不影响,可以在指定线程中存储数据,只有指定线程可以访问到
1,基本操作
public class ThreadLocalTest {
static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static class Test implements Runnable{
private static int id;
public Test(int id){
Test.id = id;
}
@Override
public void run() {
threadLocal.set(id);
System.out.println("this Thread: " + Thread.currentThread().getName() + " id: " + threadLocal.get());
}
}
public static void main(String[] args) {
Test t1 = new Test(11);
Test t2 = new Test(22);
Thread thread1 = new Thread(t1);
thread1.setName("thread1");
Thread thread2 = new Thread(t2);
thread2.setName("thread2");
thread1.start();
thread2.start();
}
}
运行结果:
2,源码分析
threadlocal
数据结构:
ThreadLocal
内部有一个静态内部类ThreadLocalMap
,该内部类才是实现线程隔离机制的关键,get()、set()、remove()
都是基于该内部类操作。
2.1 set()
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取到当前线程对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果map不为空则直接将当前threadlocal对象作为key,值作为value
if (map != null)
map.set(this, value);
else
// 否则先初始化一个map,在设置k-v
createMap(t, value);
}
每一个Thread
都有一个ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals
ThreadLocalMap
是ThreadLocal
的一个内部类,数组实现,每一个元素是一个entry
的k-v对象
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table; // 就是threadlocalmap
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) {
// if k == null but v != null 则说明这个key被回收了,因为key是弱饮用,value是强引用
// Threadlocal在这里对这种情况进行替换,减少了发生内存泄漏的概率
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
// 清除 key == null 的entry, 可以防止内存泄漏
// 若大于阈值则扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
2.2 get()
public T get() {
// 同样道理,想获取当前线程
Thread t = Thread.currentThread();
// 获取对应的threadlocalmap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 通过当前线程作为key获取对应的entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
2.3 remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
// 搜索指定key
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)]) {
if (e.get() == key) {
// entry的key设置为空
e.clear();
//处理key==null
expungeStaleEntry(i);
return;
}
}
}
3,注意事项
-
ThreadLocal
相对于synchronized
这种同步方式,ThreadLocal
是采用了以空间获取时间来提供更高效的同步方式:每个线程存储一份数据拷贝而不是通过控制对共享资源的访问来实现同步 -
ThreadLocal
为什么会产生内存泄漏?-
threadlocal
设置为null
,gc可以回收key,但是value和当前线程有强引用,所以只有当前线程销毁,才会回收value的内存,但是在threadlocal设置为null和线程结束这段时间内,value是不会被回收的,所以就有可能造成内存泄漏。在线程池中,更容易发生内存泄漏。 -
但是
threadlocal
内部其实会对key==null
的entry进行清除或替换,减少了发生内存泄漏的情况。
-