ThreadLocal 分析
ThreadLocal 作用
ThreadLocal 可以看做是一个线程的副本,每个线程中的ThreadLocal都是不一样的。访问ThreadLocal就是在访问只属于线程自己的变量。那么,这个是怎么做到,每个线程都有自己的ThreadLocal?看下面2个方法
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
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;
}
从上面2个方法中,可以很清楚的看到,对于threadlocal的get和set都是对当前线程中,一个threadlocalmap的变量修改。所以,我们在使用的时候,会发现每个线程中的threadlocal都是独立的,只属于当前线程。
例如:
public class ThreadLocalDemo {
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,100,TimeUnit.SECONDS,new LinkedBlockingDeque<>());
for(int i = 0;i<10;i++){
executor.submit(() ->{
Thread thread = Thread.currentThread();
threadLocal.set(thread.getName());
System.out.println(threadLocal.get());
});
}
executor.shutdown();
}
}
得到结果为
pool-1-thread-3
pool-1-thread-5
pool-1-thread-4
pool-1-thread-2
pool-1-thread-1
pool-1-thread-2
pool-1-thread-4
pool-1-thread-5
pool-1-thread-3
pool-1-thread-1
ThreadLocal缺点
其实ThreadLocal,在使用过程中,如果使用不正确,会造成内存泄漏。
ThreadLocalMap 中属性如下
private Entry[] table;
private int size = 0;
private int threshold;
static class Entry extends WeakReference<ThreadLocal<?>> {
//在threadlocal中value值
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以看到一个很关键的类WeakReference
,于是threadlocal中的value存在在entry中的value中,而ThreadLocalMap中的key,就是referent变量。属于弱引用。所以,当进行gc时,并且存在地方对threadlocal的引用,会存在把key回收了,但是value没有回收这样的情况。这样会造成内存的泄漏。
为了解决这一问题,threadlocal中提供了一个remove方法。如下
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//这里和hashmap中的分配桶,非常相似
int i = key.threadLocalHashCode & (len-1);
//如果i的定位不够准确,再去遍历数组
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//使用weakreference的clear方法,清除弱引用
e.clear();
//消除,key为null的元素
expungeStaleEntry(i);
return;
}
}
}
总结
ThreadLocal这里如果设计成强引用,那么,及时没有其他地方对它引用,它也不会被回收。而弱引用会被回收,只需要在使用完毕后,及时的调用remove去解决内存泄漏,便不存在问题。
引用类型
介绍一下在java中的四大引用类型如下:
- 强引用(StrongReference)
回收时机,强引用不会被GC回收,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
2.软引用(SoftReference)
回收时机,如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存,但也不一定会回收全部的软引用,更倾向于回收那些在内存中停留时间比较久的软引用
3. 弱引用(WeakReference)
回收时机,只要 GC 发现一个对象只有弱引用,则就会回收此弱引用对象。但是由于GC所在的线程优先级比较低,不会立即发现所有弱引用对象并进行回收。只要GC对它所管辖的内存区域进行扫描时发现了弱引用对象就进行回收
4.虚引用
虚引用并不会影响对象的生命周期。虚引用的作用为:跟踪垃圾回收器收集对象这一活动的情况。
当GC一旦发现了虚引用对象,则会将PhantomReference对象插入ReferenceQueue队列,而此时PhantomReference对象并没有被垃圾回收器回收,而是要等到ReferenceQueue被你真正的处理后才会被回收。