Featured image of post ThreadLocal

ThreadLocal

java.lang.ThreadLocal

ThreadLocal 是一个创建线程局部变量的类。

通常创建的共享变量是可以被任一个线程访问并修改的,存在线程安全问题。使用 ThreadLocal 创建的变量只有当前线程才可访问,其他线程无法修改和访问。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class DateUtils {
    public static final ThreadLocal<DateFormat> df = new ThreadLocal<>() {
        // 重写 initialValue 可设置初始值
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    }
}

// ---

DateUtils.df.get().format(new Date());

原理

每一个线程的 Thread 对象中都有一个 ThreadLocal.ThreadLocalMap 对象,这个对象存储了一组以 ThreadLocal.threadLocalHashCode 为键,以本地线程变量为值的 Key-Value 对。

threadlocalmap

ThreadLocal 对象就是当前线程的 ThreadLocalMap 的访问入口,每一个 ThreadLocal 对象都包含了一个独一无二的 threadLocalHashCode 值,使用这个值,就可以在线程 K-V 值对中找到对应的本地线程变量。

1
2
3
4
5
public class Thread implements Runnable {
    // ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
    // ...
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class ThreadLocal<T> {
    // ...
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Enrty(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        private Enrty[] table;
    }
    // ...
}    

set()

  • 获取当前线程
  • 利用当前线程作为句柄,获取一个 ThreadLocalMap 对象
  • 如果 ThreadLocalMap 对象不为空,则设置值,否则创建这个 ThreadLocalMap 对象并设置值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

// ---
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

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

get()

  • 获取当前线程,并获取当前线程的 ThreadLocalMap 对象
  • 如果当前线程的 ThreadLocalMap 已经初始化,返回当前 ThreadLocal 为 Key 的值
  • 如果 ThreadLocalMap 没有被初始化,或者不存在以 ThreadLocal 为 Key 的值,将创建一个新的 Value,并返回
  • initialValue 默认为 null,可进行重写
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// ThreadLocal#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();
}

// ThreadLocal#setInitialValue
private T setInitialValue() {
    T value = initialValue();  // 获得一个初始值,未重写默认为 null
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

// ThreadLocal#initialValue
protected T initialValue() {
    return null;  // 默认为 null,可被重写
}

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

remove()

  • 通过调用 ThreadLocalMap 的 remove() 方法完成移除操作
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

// ---
// 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;
        }
    }
}

内存泄露

指程序申请内存后,一直无法释放已经申请的内存。

ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 不存在外部的强引用时,下次 GC 时,该 key 就会被回收,但是 value 还存在着强引用,如果没有手动地 remove() 或者线程一直没有结束(例如使用线程池),value 就会一直存在,导致内存泄漏。

threadlocal

解决办法:

  • 每次使用完后进行 remove()

  • 使用 set()、get() 时(部分),扩容 rehash() 时(全量),会判断对 key=null 的进行清理

  • 可以将 ThreadLocal 设置为 staic final

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
private void rehash() {
    // 进行全量清理
    expungeStaleEntries();

    if (size >= threshold - threshold / 4)
        resize();
}

private void expungeStaleEntries() {
  Entry[] tab = table;
  int len = tab.length;
  for (int j = 0; j < len; j++) {
    Entry e = tab[j];
    if (e != null && e.get() == null)
      expungeStaleEntry(j);
  }
}

应用场景

  • 线程资源持有,代替参数显示传递

  • 全局存储信息,在需要的地方直接获取

  • 解决线程安全问题,达到线程隔离

参考