您的位置:首页 > 其它

ThreadLocal源码分析

2017-10-31 18:02 483 查看
    ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问时能保证各个线程里的变量相对独立于其他线程内的变量。

    一.ThreadLocalMap

    ThreadLocal类中有一个ThreadLocalMap,他是一个静态的内部类,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本,由于Key值不可重复,每一个“线程对象”对应线程的“变量副本”,而到达了线程安全。如下方法,创建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);
}
     二.ThreadLocal的获取
     在ThreadLocal的源码中,有一个get方法,他用来获取ThreadLocal,可以看出ThreadLocal变量是存储在任务对象内部的,他首先获取的是当前线程的名称,根据当前线程获取ThreadLocalMap集合。

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();
}
    1.如果当前集合不为空,会通过当前线程getEntry方法Entry对象,Entry他又是ThreadLocalMap的一个静态内部类,他继承自ThreadLocal泛型的WeakReference引用。

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}

    从这里我们可以看出参数v实际上是当前线程的变量副本的值,这样就可以获取到ThreadLocal了。
    2.如果当前集合为空,会调用setInitialValue方法,如果ThreadLocalMap中存在当前线程存在就给当前线程设置的值ThreadLocal;否则就创建当前线程的ThreadLocalMap,最后返回当前线程本地变量的值就可以获取到ThreadLocal了。源码如下:

private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
    三.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);
}
    1.获取当前所在的线程t。

    2.从ThreadLocalMap中获取本地变量。

    3.如果当前线程不为空,则调用ThreadLocalMap的set方法设置给集合设置本地变量,否则,创建ThreadLocalMap。

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

    这样就ThreadLocal的值就保存好了。

    四.ThreadLocal的内存泄漏

     从上面源码的Entry类中可以看出,他是一个弱引用,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal会被回收。如果线程死亡之后,Map的key为null,即ThreadLocal为空,这时候就会出现key为null的Entry但是任务对象可能仍然存在,使ThreadLocal的静态内部类Entry无法被回收,这样ThreadLocalMap就会出现key,就没办法获取Entry的value,导致value无法被回收。在设计中也考虑到这题,ThreadLocal在他的getEntry、set、remove、rehash等方法调用的时候都会主动清除ThreadLocalMap中key为null的Entry。因此每次使用结束时候都需要调用remove,根据线程key,从Map中移除。

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

    Thread和ThreadLocal的生命周期是一样长的,一旦使用不当,很可能引起内存泄漏,因此这里运用了WeakReference,使ThreadLocal的生命周期更紧一点,的解决了一部分内存泄漏的隐患,另一部分则是在ThreadLocal方法中清除key为null的数据。

    总结:

    1.ThreadLocal不是为了解决多线程访问共享变量,而是为每个线程创建一个单独的变量副本,提供了保持对象的方法和方便的参数传递。

    2.在每个线程中,都维护了一个ThreadLocal对象,而不是共享一个ThreadLocal。

    3.ThreadLocal作用范围是线程而不是任务,和线程的生命周期保持一致。

    4.ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程访问的并发处理;而同步机制通过锁机制保证同一时间只有一个线程访问变量,来进行多线程并发处理。ThreadLocal这种思想更为灵活,避免了复杂的数据传递和处理。

    5.使用结束时需要手动调用remove方法,避免内存泄漏。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息