ThreadLocal学习

作用

ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改

使用场景

  • 事务隔离
    Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复

Spring框架里面就是用的ThreadLocal来实现这种隔离,主要是在TransactionSynchronizationManager这个类里面

使用示例

@Slf4j
public class ThreadLocalTest {
    private static final int COUNT = 10;

    private static final ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(COUNT);

    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            threadLocal.set("Thread1");
            try {
                Thread.sleep(new Random().nextInt(10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug(threadLocal.get());
        }).start();
        new Thread(() -> {
            threadLocal.set("Thread2");
            try {
                Thread.sleep(new Random().nextInt(10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug(threadLocal.get());
        }).start();
    }
}

如何实现数据隔离

public void set(T value) {
    Thread t = Thread.currentThread();// 获取当前线程
    ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
    if (map != null) // 校验对象是否为空
        map.set(this, value); // 不为空set
    else
        createMap(t, value); // 为空创建一个map对象
}

set的源码很简单,主要就是ThreadLocalMap我们需要关注一下,而ThreadLocalMap呢是当前线程Thread一个叫threadLocals的变量中获取的

ThreadLocal学习

ThreadLocal学习

每个线程Thread都维护了自己的threadLocals变量,所以在每个线程创建ThreadLocal的时候,实际上数据是存在自己线程Thread的threadLocals变量里面的,别人没办法拿到,从而实现了隔离

ThreadLocalMap底层结构

ThreadLocal学习

长得很像Map,名字也有Map,但是没有实现Map接口,所以不是一个Map,只有一个数组

结构大体如下
ThreadLocal学习

为什么需要数组?

用数组是因为,我们开发过程中可以一个线程可以有多个TreadLocal来存放不同类型的对象的,但是他们都将放到你当前线程的ThreadLocalMap里,所以肯定要数组来存

没有了链表怎么解决Hash冲突?

ThreadLocalMap的set方法

ThreadLocal学习

ThreadLocalMap在存储的时候会给每一个ThreadLocal对象一个threadLocalHashCode,在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,int i = key.threadLocalHashCode & (len-1)

然后会判断一下:如果当前位置是空的,就初始化一个Entry对象放在位置i上

if (k == null) {
    replaceStaleEntry(key, value, i);
    return;
}

如果位置i不为空,如果这个Entry对象的key正好是即将设置的key,那么就刷新Entry中的value

if (k == key) {
    e.value = value;
    return;
}

如果位置i的不为空,而且key不等于entry,那就找下一个空位置,直到为空为止

ThreadLocal学习

在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置,set和get如果冲突严重的话,效率还是很低的

ThreadLocal的实例以及其值存放在哪?

在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存,而堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问

ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有),而ThreadLocal的值其实也是被线程实例持有,它们都是位于堆上,只是通过一些技巧将可见性修改成了线程可见

如何共享线程的ThreadLocal数据

使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值,我们在主线程中创建一个InheritableThreadLocal的实例,然后在子线程中得到这个InheritableThreadLocal实例设置的值

问题

ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。

按照道理一个线程使用完,ThreadLocalMap是应该要被清空的,但是现在线程被复用了。

那怎么解决?在代码的最后使用remove就好了,我们只要记得在使用的最后用remove把值清空就好了。

为什么ThreadLocalMap的key要设计成弱引用?

key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景。

ThreadLocal学习

上一篇:CentOS 中使用fail2ban和firewalld限制IP拦截cc攻击


下一篇:如何连接局域网共享打印机