4006668800人工服务几点,重庆百度seo排名,北京模板网站开发公司,广西桂林旅游团报价前言
记得我几年前第一次面试的时候#xff0c;就是被问了这个#xff0c;记得面试官直接就让我说说ThreadLocal的实现原理以及平时有没有见过哪些地方用到了。 我当时初入职场#xff0c;还是一个大菜鸟#xff0c;所以直接就被干蒙了#xff0c;至今还记忆犹新。
闲来…前言
记得我几年前第一次面试的时候就是被问了这个记得面试官直接就让我说说ThreadLocal的实现原理以及平时有没有见过哪些地方用到了。 我当时初入职场还是一个大菜鸟所以直接就被干蒙了至今还记忆犹新。
闲来无事总结一下这块其实仔细想想这个ThreadLocal整体思路其实挺清晰的但有些细节会有难度可能会涉及到一些比较深的平时不用的知识说实话我也还没有完全理清楚但一直都在努力中。
概念
定义
我们说的ThreadLocal是java.lang包下的一个类这个类提供特殊的线程局部变量使得每个访问该变量的线程在其内部都有一个独立的初始化变量副本。 用人话解释 先说普通类中定义的变量我们都知道是多个线程共有的。 而ThreadLocal这个类中有个特殊的变量特殊就特殊在针对不同的线程在用这个ThreadLocal的时候都能拿到本线程独有的值你可以set可以get线程之间互不影响。
其实ThreadLocal这个概念并不是java语言独有的其实很多语言都有这个概念只不过java中是用哈希表实现了这个概念。
特点
简单开销小线程安全。
哪里用ThreadLocal
1、Quartz的SimpleSemaphore提供资源隔离 看上图 SimpleSemaphore里面就有个方法obtrainLock方法用synchronized锁 这个方法中有个很重的while操作消费者处理完所有事情需要等待新的事情这个等待是一个while循环 lockName是这个方法的入参这个while方法的判断逻辑是如果locks这个HashSet中有这个lockName这个线程就执行wait()方法由于obtrainLock本身是一个所方法然后再去执行wait()你的线程就被完全阻塞在这里排队了。 试想如果没有ThreadLocal先过滤那么同一个线程的多次调用这个obtrainLock方法带着相同的lockName就会多次进入这个while循环其实同一个线程是不需要多次进入这个操作的 所以通过在这个加锁操作之前用ThreadLocal判断isLockOwner方法将同一个线程带着相同lockName调用这个方法的次数就减少到一次了即只会第一次进入while循环其他的都被isLockOwner方法挡住了 最终使得访问后面很重的操作的频率大大降低算是一个优化。
2、Mybatis的SqlSessionManager资源持有
我们知道Mybatis连数据库后会有个连接池里面会维护有多个连接每次操作数据库都需要拿到连接再去操作拿连接就是那个sqlSession.getConnection方法每次操作都可能拿到任何一个连接。 如果想要支持事务那必须让一次事务的所有操作都必须让同一个连接处理这样才能要么一起成功要么一起失败而一次事务的每个操作都需要从线程池中拿连接那如何保证一次事务的每次操作拿到的都是同一个连接呢 一次事务的多个操作一般都是一个线程去执行的那其实问题就变成如何保证一个线程拿到的总是相同的一个连接这里就用到了ThreadLocal将当前线程拿到的连接保存在ThreadLocal中下次该线程拿连接就直接从ThreadLocal中拿这个连接这样就保证了同一个线程永远拿到同一个连接而其他线程拿哪个连接不受这个线程的影响。
我们看看具体的代码实现 先是定义ThreadLocal存放的就是SqlSession每一个连接对应一个SqlSession 然后开始将一个线程的SqlSession放入ThreadLocal中 真正用的时候比如commitrollback等方法就都从ThreadLocal中获取连接了。
3、Spring的TransactionContextHolder TransactionContext也叫分布式事务资源池保存的是当前环境的上下文里面有个PlatformTransactionManager这个就是执行commit和rollback的类所以在分布式事务中也要保住同一个线程用同一个PlatformTransactionManager去执行commit或rollback所以最终TransactionContext用ThreadLocal保存起来达到效果。
4、登录
登录的时候可以把每个线程的登录信息放在ThreadLocal中就保证了同一个人的操作始终在同一个线程中。
ThreadLocal核心源码解读
1、首先每个Thread中都有一个成员变量threadLocals
这个是专门为ThreadLocal加的具体threadLocals的赋值过程是在ThreadLocal中 threadLocals的类型是ThreadLocal.ThreadLocalMap这个ThreadLocalMap是ThreadLocal中的自定义的一个内部map类key是ThreadLocal对象value是每个线程的那个独有的变量副本。
2、ThreadLocal的get方法 先拿到当前线程 getMap方法就是从当前线程中拿ThreadLocalMap这个就是Thread中那个成员变量。 ThreadLocalMap的key是当前这个ThreadLocal对象value就是我们这个get方法真正要返回的值。 如果能拿到ThreadLocalMap那么就返回ThreadLocalMap中当前ThreadLocal对象对应的value值。 如果拿不到ThreadLocalMap就去初始化value最后再返回value。
总结
我们看到ThreadLocal的实现就能清楚的知道为什么ThreadLocal可以保存不同线程的不同值了。 是因为其实最终这些值还是保存在了各个线程中的一个map中而ThreadLocal仅仅是作为这个map的一个key。 那么对于一个线程如果他遇到多个ThreadLocal其实线程中的那个map就有多对值了。 有没有一种反向操作的感觉乍一看以为这些值都是保存在ThreadLocal中的最终发现还是在线程中保存。
注意
要注意的是每个线程中的ThreadLocalMap是ThreadLocal中定义的一个静态类相当于ThreadLocal重写了一个map那有人会问了为什么不直接用HashMap呢
其实这是一个涉及到java垃圾回收的问题重写的这个ThreadLocalMap主要就是为了这个事情搞的。
我们知道其实HashMap中真正的数据是在一个个Entry中的其实ThreadLocalMap也是这样只不过ThreadLocalMap中的Entry是继承了WeakReference这个类。我们知道ThreadLocalMap中的key值其实是ThreadLocal对象在set某个对象的时候需要根据这个对象的hash值去hash表中找槽如果找到对应的槽后槽上原来的对象被回收了那对于的hash表上的位置的值就是null那么ThreadLocalMap就会对这种已经废弃掉的null值对应的槽做一些处理主要是重新回收这些槽并重新分配hash表大小等。这样相当于同步了垃圾回收的结果。
这就是为什么要重写hashMap了因为hashMap不会处理这些逻辑不处理就会造成槽不断的被已经回收的ThreadLocal的空对象占用着释放不出来最后影响hash的查找因为时间久了每次正常hash后应该放的槽都被null占了只能继续向后移着放。