线程封闭与ThreadLocal

2022-06-20 20:03:27 浏览数 (1)

线程封闭是指为避免多线程间的数据共享和同步, 而仅在单线程内进行的数据访问方式.

通常有三种策略:

1. Ad-hoc线程封闭

一种靠开发人员编程保证线程安全的开发方式, 几乎没人使用.

2. 栈封闭

利用局部变量不被多个线程所共享的特性, 避免多线程的并发问题. 关于栈结构可以阅读JVM栈.

所以尽量使用局部变量代替全局变量.

3. ThreadLocal类

将创建的变量与线程关联到一起去, 用空间换时间的方式, 解决线程共享数据的问题.

ThreadLocal

ThreadLocal主要由线程, ThreadLocal, 变量三方面配合使用, 下面简单说下三者之间的关系.

线程和ThreadLocal是1对多的关系, 每个ThreadLocal会保存在线程中的ThreadLocalMap(Map结构)中.

参照ThreadLocal源码, 在保存变量时, 会先根据当前线程取ThreadLocalMap变量.

代码语言:javascript复制
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadLocal与变量是1对1的关系, 两种是保存在ThreadLocalMap中的Entry中. 其中ThreadLocal是Entry.key; 变量是Entry.value;

参看ThreadLocalMap, 保存变量set()方法

代码语言:javascript复制
static class ThreadLocalMap {
    private Entry[] table;
  private void set(ThreadLocal<?> key, Object value) {
    ...
      tab[i] = new Entry(key, value);
    ...
  }
}

在JVM内存中各部分关系如下:

注意Entry.key指向ThreadLocal变量是使用的虚线. 是因为Entry.key是采用的弱引用.

代码语言:javascript复制
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

内存泄漏

弱引用对象, 在没有外部对象引用时, 会被回收掉.

在方法执行结束, 而ThreadLocal没有调用remove()方法释放Entry时, 会因为ThreadLocal变量因为是弱引用被回收掉, Entry.key会指向null, 而Entry和Entry.value还是会指向原对象, 并不会被回收掉.

这在线程执行时间很长或者是线程池中的线程重复使用时, 会无法正确回收对象导致内存泄漏.

内存泄漏优化

为避免内存泄漏的问题, ThreadLocal中也进行了优化.

针对Entry.key是null的情况进行了替换处理, 减少了内存泄漏的风险.

代码语言:javascript复制
private void set(ThreadLocal<?> key, Object value) {
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
  }

最好的方式还是在finally中回收掉数据

代码语言:javascript复制
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
try{
    threadLocal.set(1);
    // ...
} finally {
    threadLocal.remove();
}

0 人点赞