前言
不知道你有没有在写WEB项目的时候,在Service或DAO里面,想要取得用户信息(如权限验证),而不得不将相关数据做为参数一层一层的传递,这时候你是不是想有一个方式,可以直接得到该信息?
不知道你有没有在成员变量中放置一些公共数据,但又不能保证线程安全的情况呢?
ThreadLocal你值得拥有,它能完美的帮助你完成你前面的设想。
When? Where? Why?
对于ThreadLocal,相信如果没有了解及使用过的朋友,一定会有这3W问题,下面就根据自己遇到的情况,说一下相关的理解。
适用的情况:
- 当一个对象不是线程安全的,但你又不想用同步锁的时候。
- 在同一个线程中,不通过传参,但想取到数据的情况。比如Web项目,一个操作涉及到很多层的很多方法,需要记录操作请求,比如哪个用户操作的,如果作为参数传输当然是可以的,只是有了ThreadLocal,就没必要这样作了。
- Spring创建的对象的规则,默认只创建一个实例,在这样的情况下,成员变量就不是线程安全的,如果想在方法中调用成员变量的数据时。
How to use ThreadLocal
以codelogger-web为例,我定义了一个MarvelServlet的类:
public class MarvelServlet {
private HttpServletRequest request;
private HttpServletResponse response;
/* getters and setters here */
}
里面存放了Web请求的request和response,然后定义了一个MarvelFilter类:
public abstract class MarvelFilter<T extends MarvelServlet> implements Filter {
private static ThreadLocal<MarvelServlet> filterThreadLocal = new ThreadLocal<MarvelServlet>();
public static <T extends MarvelServlet> T getMarvelServlet() {
@SuppressWarnings("unchecked")
T marvelServlet = (T) filterThreadLocal.get();
return marvelServlet;
}
public abstract T buildMarvelServlet();
public abstract void doCustomFilter(ServletRequest request, ServletResponse response,
FilterChain filterChain) throws IOException, ServletException;
@Override
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
MarvelServlet marvelServlet = buildMarvelServlet();
marvelServlet.setRequest((HttpServletRequest) request);
marvelServlet.setResponse((HttpServletResponse) response);
filterThreadLocal.set(marvelServlet);
doCustomFilter(request, response, filterChain);
filterChain.doFilter(request, response);
}
}
然后把这个Filter加到web.xml中去:
<filter>
<filter-name>marvelFilter</filter-name>
<filter-class>org.codelogger.web.filter.MarvelFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>marvelFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这样,当用户发送一个请求过来时,我们在Service或DAO中,只需要调用
MarvelServlet marvelServlet = MarvelFilter.getMarvelServlet();
就能得到当前请求的request和response了,这样省去了参数的传递,减少依赖关系和代码维护。
下面再例一个多线程共享同一个成员变量的示例:
import static java.lang.String.format;
public class ThreadLocalTest implements Runnable {
private static ThreadLocal<Integer> counter = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(format("%s: %s", Thread.currentThread().getName(), getNumber()));
}
}
public Integer getNumber() {
counter.set(counter.get() + 1);
return counter.get();
}
public static void main(final String[] args) {
ThreadLocalTest threadLocalTest = new ThreadLocalTest();
Thread thread1 = new Thread(threadLocalTest);
Thread thread2 = new Thread(threadLocalTest);
Thread thread3 = new Thread(threadLocalTest);
thread1.start();
thread2.start();
thread3.start();
}
}
输出的结果为:
Thread-0: 1
Thread-1: 1
Thread-0: 2
Thread-0: 3
Thread-2: 1
Thread-1: 2
Thread-1: 3
Thread-2: 2
Thread-2: 3
后记
想想以前为了做到线程安全所做出的种种,如synchronized
,static的ConcurrentHashMap
,现在看来,也是一种难得的经历,经历过总是有收获的。
平常多看看相关的资讯,能帮助我们少走不少弯路。
这是一个自己实现的伪ThreadLocal,见笑了:
public class MyThreadLocal<T> {
private Map<Thread, T> container = new ConcurrentHashMap<Thread, T>();
public void set(final T value) {
container.put(Thread.currentThread(), value);
}
public T get() {
Thread thread = Thread.currentThread();
T value = container.get(thread);
if (value == null && !container.containsKey(thread)) {
value = initialValue();
container.put(thread, value);
}
return value;
}
public void remove() {
container.remove(Thread.currentThread());
}
protected T initialValue() {
return null;
}
}
当然,这个MyThreadLocal并不包含Thread的监听并做出相应的处理,所以在Thread被销毁的时候,其中的数据一样存在,并且不会被GC回收,所以要走的路还很远。

