ThreadLocal的深入理解及应用

是什么

ThreadLocal很容易让人望文生义,想当然地认为是一个“本地线程”。其实,ThreadLocal并不是一个Thread , 它类似(Map),用来存储当前运行线程及对应的变量。

在WEB应用中每次Http请求,都相当于从线程池取一个空闲线程对请求的方法作处理。此时当前线程的所有方法中Thread.currentThread()都是一样,然后我们可以把它假想为Map的key,value可以存一个在整个请求中都用到的变量。

好处

1:避免了传参

例如在dao层 userDao下有save(User user)update(Department department)两种方法,

service中accessionServiceaccession方法

boolean accssion(User user, Department department, Connection conn) {

    conn.begin();

    save(user);

   update(department);

   conn.commint();

}

为了保证数据一致性,必须在service层添加事务,那么每有一个新的连接(请求),必须要传一个conn参数,做法不好,污染api。

2:解决了高并发中对资源的争夺(牺牲空间换取时间与synchronize相反)。

 

ThreadLocal源码如下


    //一般我们用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(); //初始化ThreadLocalMap
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    //ThreadLocalMap其实是一个entry(静态内部类)继承了WeakReference,是一个弱关联,并非数组
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
    //改变当前线程绑定值
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    //map.set()具体实现,根据当前线程key定位到entry[i],再改变entry[i]对应的值为value
    private void set(ThreadLocal<?> key, Object value) {

        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1);

        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
             ThreadLocal<?> k = e.get();

             if (k == key) {
                 e.value = value;
                 return;
             }

             if (k == null) {
                 replaceStaleEntry(key, value, i);
                 return;
             }
        }

        tab[i] = new Entry(key, value);
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
 
应用

在spring切面方法中,我们没有办法直接从ProceedingJoinPoint中获取request和response对象,但是我们要对request的传递参数处理,同时也要用response添加cookie,实现重定向等功能。

//过滤器,每次请求过来都经过这个过滤器处理
public class GetContent implements Filter {
    @Override  
    public void destroy() {  
        // TODO Auto-generated method stub  
    }  
    @Override  
    public void doFilter(ServletRequest arg0, ServletResponse arg1,
                         FilterChain arg2) throws IOException, ServletException {
        SysContent.setRequest((HttpServletRequest) arg0);
        SysContent.setResponse((HttpServletResponse) arg1);
        arg2.doFilter(arg0, arg1);  
    }  
    @Override  
    public void init(FilterConfig arg0) throws ServletException {
        // TODO Auto-generated method stub  
    }  
}  
//把response、request与当前线程绑定,具体实现
public class SysContent {
     private static ThreadLocal<HttpServletRequest> requestLocal= new   ThreadLocal<HttpServletRequest>();
     private static ThreadLocal<HttpServletResponse> responseLocal= new ThreadLocal<HttpServletResponse>();
       
    public static HttpServletRequest getRequest() {  
        return (HttpServletRequest)requestLocal.get();  
    }  
    public static void setRequest(HttpServletRequest request) {  
        requestLocal.set(request);  
    }  
    public static HttpServletResponse getResponse() {  
        return (HttpServletResponse)responseLocal.get();  
    }  
    public static void setResponse(HttpServletResponse response) {  
        responseLocal.set(response);  
    }  
    public static HttpSession getSession() {
        return (HttpSession)((HttpServletRequest)requestLocal.get()).getSession();  
    }  
}
@Component
@Aspect
public class ActionShareAspect {
    private static final Logger logger = LoggerFactory.getLogger(ActionLogAspect.class);

    //配置切入点
    @Pointcut("execution(public * com.xx.xx.*.*(..))") // 配置切入点
    public void pointcut(){}

    @Around("pointcut()")
    public Object aroundMethod(ProceedingJoinPoint pjd) {
        //request、response获取
        HttpServletRequest request = SysContent.getRequest();
        HttpServletResponse response = SysContent.getResponse();

        //执行目标方法
        String methodName = pjd.getSignature().getName();
        Object result = null;
        try {
           //...略
           //返回通知
           result = pjd.proceed();
           return result;
        } catch (Exception e) {
            //...
        }
    }

回顾来思考最初的问题:关于spring的事务。我们可以把每次请求从数据连接池获取的connection绑定到ThreadLocal上,那么就解决了service中事务的问题。当然这里要对service进行切面编程,使用代理,下次博客再细说。

转载于:https://my.oschina.net/u/2858987/blog/891789

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值