ThreadLocal原理和项目中如何使用

来源:互联网 发布:数据粒度和维度 编辑:程序博客网 时间:2024/04/30 16:40

本教程分如下三个部分
1. 项目中如何使用Threadlocal
2. Threadlocal和Thread关系以及Threadlocal源码分析
3. Threadlocal的DEMO练习(提供github和码云下载源代码)

首先上干货。讲讲我司项目中如何使用ThreadLocal。
会话保持静态类
这是一个登录会话保持的静态类,用来保存当前线程的登录信息。
使用AssertionContent原因:
由于通过ServletRequest request转换成

HttpServletRequest httpRequest = ( HttpServletRequest )request;

可以通过HttpSession session = httpRequest.getSession( false );得到session。但是,并不是每个业务类或者方法都能得到当前的httpRequest。所以,就可以使用ThreadLocal在任何当前线程的任何业务类中,得到session。
在登录的filter中,可以通过session.setAttribute( BrowserSession.ASSERTION, bs );将当前BrowserSession保存到会话中。

BrowserSession bs = new BrowserSession();        bs.setUid(userId);        bs.setUname(name);        ...

BrowserSession 是当前用户登录的一些个人信息。是业务自定义的实体类。
下一次再访问的时候,可以直接从session中得到用户所有信息。

在业务相关的filter中,使用线程变量Threadlocal保持会话
其中set方法如下:

public void setAttribute( String name, Object value ) {        attributes.put( name, value );    }

以上set方法就是将session会话保存到AssertionContext
initContext
注意:此处是用一个AssertionContext中的一个Threadlocal变量保存了session。
所以在取session的时候,使用如下图所示方法:
取session
其中AssertionContex.getContext()方法大致如下:

public static AssertionContext getContext() {        AssertionContext context = contextHolder.get();        ...        return context;    }

其实也可以将以上步骤放到一个filter中,怎么顺手怎么使用。
接下来,就是在任何你想要使用session的地方,取得session,比如在业务service中:
业务service
其中checkLogin方法就是通过上面说到的先得到AssertionContext 中的Threadlocal再得到session来获得。

以上是讲解如何在业务中使用Threadlocal,下面结合源码介绍下原理:
Threadlocal设计两个java类:Thread和Threadlocal。
首先讲解下他们之间的关系

Thread和Threadlocal关系
可以看到,Thread有个成员变量ThreadLocal.ThreadLocalMap,而ThreadLocalMap是Threadlocal的内部静态类。ThreadLocalMap中存的值的key为this,即当前Threadlocal类的引用,这样,每次从同一个Threadlocal和同一个Thread中得到的值就唯一确定了。
看一下Threadlocal的set方法:

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

首先得到当前线程,然后得到当前线程的ThreadlocalMap。由于只要线程确定,所以map就确定。
getMap方法如下:

 ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }

直接放回当前线程的成员变量。
可以看到第一次得到的map肯定为null,我们接下来看createMap方法:

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

直接new了一个ThreadLocalMap,其中key为当前Threadlocal的引用。
如果map的值不为null则直接把值set到map中去。
看完set方法接着看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();    }

首先,还是得到当前线程,得到当前线程的map,如果map不为空,则:
得到key为this的值,然后返回该值。如果map为空则调用setInitialValue方法

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

继续判断map是否为空(这里再进行判断,我认为可能是在多个线程并发执行的情况下,如果执行到方法的入口处,其他的线程又new了一个ThreadlocalMap,辣么此处就不需要再new了),这里的initialValue()方法直接放回的是一个null。
到此,Threadlocal的set和get方法和原理都介绍完了。需要注意的是,由于ThreadlocalMap中的key是this引用,也就是说,this引用如果指向不同的对象,辣么通过get方法得到的值就不是希望得到的那个值。所以,要想每次得到的值都是正确的,必须使this指针指向的对象唯一,这就解释了为什么Threadlocal都使用静态变量来保存。

为了更好的理解Threadlocal的原理,下面有几个Threadlocal的demo练习,非常简单。大家可以clone下来或者fork下来试试。
git地址:
https://github.com/tengqingya/ThreadLocalPractice
码云地址:
https://git.oschina.net/tengqingya/ThreadLocalPractice

请尊重作者和版权,转载请标明出处。
联系作者:qq475804848,滕庆亚

4 0
原创粉丝点击