> 文章列表 > Java源码(一)

Java源码(一)

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定义就解决问题了,修改后的如下图(其中只保留部分使用到的关键代码):

Java源码(一)

为什么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!

欢迎点赞、关注、留言,一起学习、交流!