shiro的ThreadContext和ThreadLocal


问题

今天又看了一下之前用shiro框架写的项目,仔细看了一下里面的源码,突然有一个好奇点,我们一般用的是SecurityUtils.getSubject();方法来获得当前用户的,这个类是shiro下自带的类,也就是说在我们登录以后shiro已经封装了登录过的对象到内部,于是我想到,如果不同的用户同时访问登录接口进行登录,shiro是怎么判断这个会话是哪个用户呢?而且我看到它内部并不是生成一个ioc bean交给spring管理,可以说shiro有一个自己的安全管理器,一切活主要交由它的安全管理器负责,那么对于不同线程它是怎么处理的呢,来看它的getSubject()方法:

    public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }

可以看到这里有一个新类ThreadContext,用户信息便是从这个类中的getSubject方法获得的

ThreadContext

来看ThreadContext:
ThreadContext 提供了一种基于键值对将对象绑定和解除绑定到当前线程的方法。内部 HashMap 用于维护每个线程的键值对。如果期望的行为是确保绑定数据不会在池化或可重用线程环境中的线程之间共享,则应用程序(或更可能是框架)必须分别在堆栈执行的开始和结束时绑定和删除任何必要的值(即单独显式或全部通过 clear 方法)。

这个注释说明白了,是一个内部的hashmap维护键值对的,来看是什么样的hashmap呢:

public abstract class ThreadContext {


    public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
    public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
//重点在这里
    private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();

这个静态final类型的变量:
private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问, 内部是一个ThreadLocal来将线程之间彼此隔离开的,ThreadLocal叫做线程本地变量,也就是ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量。

上面的resouces引用指向了一个类InheritableThreadLocalMap,来看源码:

  private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> {

    
        @SuppressWarnings({"unchecked"})
        protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {
            if (parentValue != null) {
                return (Map<Object, Object>) ((HashMap<Object, Object>) parentValue).clone();
            } else {
                return null;
            }
        }
    }
}

似乎没什么特别的,只是返回了一个参数的副本
再来看InheritableThreadLocal:
在这里自定义了一个ThreadLocal,实现了创建一个新map 返回父线程的值和获得当前map

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    } 
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal看完以后,我们再回到刚刚说的ThreadContext,我们知道它内部是由一个ThreadLocalMap的引用,下面来看ThreadContext中的重点方法:

    public static Map<Object, Object> getResources() {
        if (resources.get() == null){
            return Collections.emptyMap();
        } else {
            return new HashMap<Object, Object>(resources.get());
        }
    }

在上面getResources方法中获得了当前map引用中的值,其实resources.get()方法便是调用ThreadLocal的get方法,获取当前变量中之前put进去的值:

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

我们再看put方法:

//ThreadContext的put方法
 public static void put(Object key, Object value) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }

        if (value == null) {
            remove(key);
            return;
        }

        ensureResourcesInitialized();
        resources.get().put(key, value);

        if (log.isTraceEnabled()) {
            String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
                    key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }

调用put方法的是下面的bind方法:

  public static void bind(SecurityManager securityManager) {
        if (securityManager != null) {
            put(SECURITY_MANAGER_KEY, securityManager);
        }
    }

到这里便非常清楚了,它其实是放了一个以ThreadContext.class.getName() + “_SECURITY_MANAGER_KEY”;为key,当前线程的securityManager为Value到map中,而securityManager是一个有login、createSubject方法的接口,在shiro登录的时候最后会走到securityManager的实现类中login方法中,而这个用户最终会通过threadlocalmap放入到当前线程中。
那么下面再来学一下ThreadLocal吧!

ThreadLocal

ThreadLocal: 它的作用只是在当前线程中,共享一个值,可以是session connection等等,这个值在每个线程都是不一样的

那么为什么它是用Static修饰的呢?

从Java官方文档中的描述:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。
ThreadLocal的原理是在Thread内部有一个ThreadLocalMap的集合对象,他的key是ThreadLocal,value就是你要存储的变量副本,
不同的线程他的ThreadLocalMap是隔离开的,如果变量ThreadLocal是非static的就会造成每次生成实例都要生成不同的ThreadLocal对象,虽然这样程序不会有什么异常,但是会浪费内存资源.造成内存泄漏.

ThreadLocal使用案例
可以通过ThreadLocal存放JDBC连接,已达到控制事物的效果
比如 当你修改一个对数据库进行操作的时候需要记录日志,这个时候单线程跑代码是可以的,当出现两个或多个以上线程去对数据库进行操作的话就有可能出现前面一个线程把连接给关掉了的情况,后面线程再去使用这个连接的时候就会出现连接已经被关闭异常。

解决问题
每个线程都对应一个数据库连接,你不要管我的,我也不管你的,最后由事物统一提交操作就不会遇到上面的问题了,所以可以用ThreadLocal这个对象来解决这个问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值