关于ThreadLocal的说明
2019-07-30 本文已影响0人
华木公子
一 ThreadLocal的使用场景
当多个线程需要使用同一共享变量,但是每个线程又希望有自己独立的值,互不干扰时。
二 ThreadLocal的原理
- 在Thread类中,有一个特殊的局部变量ThreadMap,这个Map不是通用Map,是单独构造的一个类似Map类的类。
public
class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
当Thread实例化时,一个thread就有一个对应的Map(私有)
- 当在thread线程中对threadlocal共享变量进行存值操作时,当前线程的私有Map会把threadlocal变量this作为key存储,值为对应需保存的value。
当在thread线程中对threadlocal共享变量进行取值操作时,当前线程的私有Map会把threadlocal变量this作为key从map中取值,所获得的值为对应之前保留的value。
由于map是私有的,所以其他线程是不可能操作这个线程的map,从而也就达到了互不影响的效果。
【测试代码如下】:
public class ThreadLocalDemo {
private static ThreadLocal<String> myLocal = new ThreadLocal<String>();
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
myLocal.set("A");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(myLocal.get());
}
});
myLocal.set("main");
threadA.start();
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(myLocal.get());
}
}
当对myLocal.set("A");执行时,ThreadLocal代码如下:
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
}
- 一个threadlocal变量对应一个值,一个线程中可以使用多个threadlocal变量来存取多个值。但是一个线程只有一个map,所以可能会存在key冲突的问题,那么当key的hashcode冲突时,就会继续往下一个数组空间存放,直到不冲突。这就存在效率问题,取值时,就需要根据hashcode比较,取出threadlocal对象后还要比较(==)是否是同一个对象,如果是同一个对象,才会去取它对应的值。
ThreadLocal.ThreadLoclMap 的代码如下:
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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();
}
三 ThreadLocal使用的注意事项
- 内存泄漏问题
threadmap的entry本身key是弱引用可以被回收,但是value是强引用,不会被GC回收。如果使用了线程池,那么value就会一直存在,导致内存泄漏。