ThreadLocal
怎么理解呢,在我看来就是给每一个Thread提供私有的空间,用来存放数据,这个数据可以再在Thread整个存活周期内被拿到
ThreadLocal 的两种典型的使用场景
1、任务多且每一个都需要,存在并发安全,比如SimpleDateFormat ,每一个任务都需要,但是又不是并发安全的类,如果在每一个任务里面都new一个SimpleDateFormat 对象的话,太占用内存了,如果只new一个SimpleDateFormat 对象的话有需要加锁,影响并发性能,但是这个类只是拿来做格式化,因为这个影响性能很明显划不着,这个时候就直接使用ThreadLocal ,给每一个线程创建一个SimpleDateFormat 对象,在线程池中的线程是可以服用的,所以并不会创建很多线程
2、每一个线程需要单独保存信息,在一个Thread里面执行了多个方法或者方法里面在调用其他方法,可以利用ThreadLocal 来传递参数,比如说用户信息,但是每一个线程的用户信息肯定是不一定一样的
public class LocalData {
public String id;
@Override
public String toString() {
return "LocalData{" +
"id='" + id + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public class LongLocal {
public static ThreadLocal<LocalData> longThreadLocal=new ThreadLocal<LocalData>(){
@Override
protected LocalData initialValue() {
LocalData localData = new LocalData();
System.out.println("初始化的hashcode:"+localData.hashCode());
return localData;
}
};
}
public class LocalThreadTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 5, TimeUnit.SECONDS, new LinkedBlockingQueue());
for (int i = 0; i < 100; i++) {
threadPoolExecutor.submit(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--"+LongLocal.longThreadLocal.get().hashCode());
});
}
threadPoolExecutor.shutdown();
}
}
初始化的hashcode:541662399
初始化的hashcode:699997381
初始化的hashcode:2098748799
pool-1-thread-10--2098748799
初始化的hashcode:1915851000
初始化的hashcode:2057263935
初始化的hashcode:993438493
初始化的hashcode:1392591561
初始化的hashcode:1532882474
初始化的hashcode:1182696684
初始化的hashcode:136556034
pool-1-thread-6--1182696684
pool-1-thread-8--1532882474
pool-1-thread-5--1392591561
pool-1-thread-1--993438493
pool-1-thread-9--2057263935
pool-1-thread-2--1915851000
pool-1-thread-3--699997381
pool-1-thread-7--541662399
pool-1-thread-4--136556034
pool-1-thread-1--993438493
pool-1-thread-8--1532882474
pool-1-thread-10--2098748799
pool-1-thread-5--1392591561
pool-1-thread-4--136556034
pool-1-thread-7--541662399
pool-1-thread-6--1182696684
可以发现每一个线程拿到的hashcode都是不同的,并且重复使用线程执行任务得到的hashcode是一样的,
对于共享的对象,如果使用ThreadLocal 可以理解为用空间换时间,就是每一个线程拷贝一个副本到自己线程,然后自己玩自己的,互不干扰,避免了资源的竞争
但是ThreadLocal 并不能解决共享资源的并发问题,因为对于 ThreadLocal 而言,每个线程中的资源并不共享
Thread、 ThreadLocal 及 ThreadLocalMap 的关系

每一个Thread都有一个自己的ThreadLocalMap ,一个ThreadLocalMap 里面可能放了很多个ThreadLocal
//ThreadLocal 的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);
}
通过map.set(this, value);可以看出在存储数据到ThreadLocalMap 里面的时候key是当前ThreadLocal 自身,值是要保存的对象
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
}
ThreadLocalMap会有一个 Entry 类型的数组,名字叫 table。Entry 是键值对类型
ThreadLocalMap在存数据时如果发生hash冲突的时候,使用的是线性探测法,就是会寻找下一个空的位置,而不是和hashmap一样使用链表往下接
ThreadLocal的内存泄漏问题
从Entry extends WeakReference<ThreadLocal<?>>发现,key也就是ThreadLocal其实使用的是弱引用,在GC的时候是可以被回收的,也就是key不会发现内存泄漏问题
从value = v;发现,value也就是保存的数据使用的是强引用,在GC的时候是不会被回收的,也就是value是会发现内存泄漏问题(如果线程一直在执行耗时的任务不停止,jvm执行可达性分析的时候是会存在value的强引用的)
如果想避免value的内存泄漏,可以将value置为null
//ThreadLocalMap的remove方法
private void remove(ThreadLocal<?> 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) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
从源码中可以发现,remove方法里面有e.value = null;是将value置为了null,也就可以解决内存泄漏的问题,如果后面我们不在使用ThreadLocal中的数据的话,可以调用remove将value置为null,解决内存泄漏
979

被折叠的 条评论
为什么被折叠?



