Java源码(一)
思维导图
一、ThreadLocal
1.场景
项目采用SSM+Shiro登录认证,改造需求如下:
后台管理员登录需要限制,同一个用户的不同IP需要通过过自定义验证后才能登录。
2.问题
- 在完成需求后发现有管理员用户(这里就用A)通过验证登录了,那么后面登录的管理员用户(这里就用B、C等)可能会直接跳过验证就直接登录了。这肯定不符合要求!
3.问题解决
3.1 Debug
- 因为是在原来shiro框架自定义过滤器AuthenticationFilter基础上添加了用户ID+IP验证,过滤器中涉及到生成token的createToken方法和认证成功后登录方法executeLogin,所以在该类设置了一个全局私有变量isWebLogin 来判断是否通过自定义验证。
- 而isWebLogin所有线程(即所有用户)都可以访问并修改,所以导致上述问题。
3.2 解决
- 那如何让isWebLogin变量的每个线程都拥有自己的专属线程本地变量,且每个线程的访问及修改都互不影响呢?
- ThreadLocal变量不就正好可以解决这个问题吗?于是isWebLogin使用ThreadLocal定义就解决问题了,修改后的如下图(其中只保留部分使用到的关键代码):
为什么ThreadLocal能够解决这个问题?且看下面ThreadLocal及其源码分析:
4.ThreadLocal
关于ThreadLocal的基础介绍,见我另一篇文章:
Java并发编程(一)常见知识点 — 21 ThreadLocal
5.ThreadLocal源码分析
5.1 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();}
逻辑如下:
- 获取当前线程内部的ThreadLocalMap
- map存在则获取当前ThreadLocal对应的value值
- map不存在或者找不到value值,则调用setInitialValue,进行初始化
5.2 setInitialValue()源码
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}
逻辑如下:
- 调用initialValue方法,获取初始化值【调用者通过覆盖该方法,设置自己的初始化值】
- 获取当前线程内部的ThreadLocalMap
- map存在则把当前ThreadLocal和value添加到map中
- map不存在则创建一个ThreadLocalMap,保存到当前线程内部
5.3 set(T value)源码
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}
逻辑如下:
- 获取当前线程内部的ThreadLocalMap
- map存在则把当前ThreadLocal和value添加到map中
- map不存在则创建一个ThreadLocalMap,保存到当前线程内部
5.4 remove()源码
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}
逻辑如下:
- 获取当前线程内部的ThreadLocalMap,存在则从map中删除这个ThreadLocal对象。
6.ThreadLocal使用注意
6.1 内存泄露
- 因为ThreadLocal 使用的是ThreadLocalMap
- 而ThreadLocalMap中使用的 key 为ThreadLocal 的弱引用,而value是强引用。
- ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。
- ThreadLocalMap中就会出现 key 为null的 Entry。value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。
6.2 如何避免
threadLocalMap在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录,但是还是有可能有编码导致内存泄露,所以我们还需要从以下方面去避免:
-
使用完 ThreadLocal方法后最好手动调用remove()方法
-
将ThreadLocal变量定义为private static
下一篇跳转—Java源码(一)
本篇文章主要参考链接如下:
参考链接1-JDK 源码解析——线程变量 ThreadLocal
参考链接2-JavaGuide
持续更新中…
随心所往,看见未来。Follow your heart,see light!
欢迎点赞、关注、留言,一起学习、交流!