乐趣区

关于多线程:Java线程封闭

把对象封装到一个线程里, 只有一个线程能够看到该对象, 那么就算这个对象不是线程平安的, 也不会呈现任何线程问题, 因为它只能在一个线程中被拜访。

  • Ad-hoc 线程关闭: 程序控制实现, 十分软弱, 最蹩脚, 疏忽。
  • 堆栈关闭: 简略的说就是局部变量, 无并发问题。多线程拜访同一个办法时, 办法中的局部变量会被拷贝一份到线程栈中。办法的局部变量不是被多线程共享的, 不会呈现线程平安问题, 能用局部变量就不要用全局变量, 全局变量容易产生并发问题, 留神全局变量不是全局常量。
  • ThreadLocal 线程关闭:Java 中提供一个 ThreadLocal 类来实现线程关闭, 这个类使线程中的某个值与保留值的对象关联起来

ThreadLocal

ThreadLocal 类提供的办法

外围的五个操作: 创立, 创立并赋初始值, 赋值, 取值, 删除

  • 创立:
private final static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>;
  • 创立并赋初始值
private final static ThreadLocal<String> threadLocal=new ThreadLocal<String>(){
        @Override
        protected String initialValue() {return "入门小站";}
};
  • 赋值
threadLocal.set("入门小站");
  • 取值
threadLocal.get();
  • 删除
threadLocal.remove();

实现原理

首先 ThreadLocal 是一个泛型类, 保障能够承受任何类型的对象。

一个线程内能够存在多个 ThreadLocal,ThreadLocal 外部保护了一个 Map, 这个Map 不是 HashMap, 而是ThreadLocal 实现的一个 ThreadLocalMap 的动态外部类。咱们应用的 get(),set() 办法其实是调用了这个 ThreadLocalMap 类对应的get(),set()

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();}
    

public void set(T value) {Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

调用 ThreadLocal 的 set 办法时, 先获取以后的线程 Thread t = Thread.currentThread();, 而后获取以后线程保护的ThreadLocalMap。如果ThreadLocalMap 不存在则初始化。

ThreadLocalMapmap.set(this, value); 第一个参数是 this,this 指的是以后的 ThreadLocal, 就是下面代码外面的threadLocal 变量。

最终的变量是放在以后线程的 ThreadLocalMap 中, 并不是存在 ThreadLocal 上,ThreadLocal能够了解成传递关系的。

内存透露问题

ThreadLocalMap中应用的 keyThreadLocal的弱援用, 弱援用的特点是, 如果这个对象只存在弱援用, 那么在下一次垃圾回收的时候必然会被清理掉。

所以 ThreadLocal 没有被强援用的状况下, 在垃圾回收的时候会被清理掉, 然而 value 却是强援用, 不会被清理, 这样的话就出呈现 keynullvalue

ThreadLocalMap实现中曾经思考了这个状况, 在调用 set,get,remove 办法的时候会清理掉 keynull的记录。如果呈现了内存透露, 那就是说在 keynull后, 没有手动调用 remove 办法, 并且之后也不再调用 set,get,remove 办法。

内存透露解决方案

将 ThreadLocal 变量定义成 private static 的,这样的话 ThreadLocal 的生命周期就更长,因为始终存在 ThreadLocal 的强援用,所以 ThreadLocal 也就不会被回收,也就能保障任何时候都能依据 ThreadLocal 的弱援用拜访到 Entry 的 value 值,而后 remove 它,避免内存泄露。

如何保障两个同时实例化的 ThreadLocal 对象有不同的 threadLocalHashCode 属性

在 ThreadLocal 类中,还蕴含了一个 static 润饰的 AtomicInteger([əˈtɒmɪk]提供原子操作的 Integer 类)成员变量(即类变量)和一个 static final 润饰的常量(作为两个相邻 nextHashCode 的差值)。因为 nextHashCode 是类变量,所以每一次调用 ThreadLocal 类都能够保障 nextHashCode 被更新到新的值,并且下一次调用 ThreadLocal 类这个被更新的值依然可用,同时 AtomicInteger 保障了 nextHashCode 自增的原子性。

ThreadLocal利用

场景一

Web我的项目公共参数从 controller 层传递到 service 层,再从 service 层传递到 mapper 层,或者从 service 层传递到其余的工具类当中。为了防止参数简单的传递,在 controller 中将曾经封装好的参数放入 ThreadLocal 中,在其余层调用时间接通过 ThreadLocal 对象获取。在办法完结时,定义拦截器 (HandlerInterceptorAdapter)(或者 Filter) 进行 ThreadLocal 的 remove 办法。

场景二

在须要登录的零碎中用户信息经常存在 Sessiontoken。比方咱们要从 Session 中获取用户信息须要在接口参数中加上 HttpServletRequest 对象,而后调用 getSession 办法,且每一个须要用户信息的接口都要加上这个参数,能力获取 Session, 比拟麻烦。

这个时候咱们就能够用 ThreadLocal, 在拦截器(HandlerInterceptorAdapter)(或者 Filter) 中解析获取用户信息, 而后保留到 ThreadLocal, 业务逻辑间接在ThreadLocal 中获取就能够了。

【关注微信公众号:【入门小站】解锁更多知识点】

退出移动版