我们经常听说可以使用ThreaLocal解决多线程的线程安全问题,ThreaLocal为什么可以解决这个问题以及具体怎么使用呢?
一般提到多线程自然而然就会想到锁机制,那么这里为什么不用锁而是用ThreaLocal呢?这里就涉及到ThreaLocal的使用场景了,其实ThreaLocal设计目的和使用场景是实现一个线程内部变量的共享,跟多线程之间共享变量完全是两码事。
那么线程内部变量为什么要共享?共享给谁?为什么要共享那当然是需要用到这个变量值,共享给这个线程中运行的所有方法。这里其实会涉及到Java内存模型相关的知识,为了方便理解举一个例子:假设A方法中定义了一个变量,再后续的B、C、D方法中都会用到这个变量,我们常用的办法都是将这个变量通过方法参数传递。每次都要传很多参数比较繁琐,那有没有可以不用传这个参数,但需要用到的时候又可以拿得到呢?答案是肯定的,那就是ThreaLocal。
我们可以将后续需要用到的变量放到ThreaLocal中,准确的说是放到当前线程Thread的一个成员变量ThreaLocal.ThreaLocalMap threadlocals里面(这个有点绕,比理解先跳过,就当作是放在ThreaLocal里)。这时候你会不会有一个疑问:那是不是还得把ThreaLocal往后面的方法传呢?自然是不用的,如果这样做跟直接传变量就没啥区别了,相当于又回到了原点。
不用传参那具体怎么用呢?聪明的你应该想到了,把变量定义为static的。这样就可以做到不用传参数,又随时可以拿到,跟平时定义一个常量类的玩法一样。看到这里如果觉得豁然开朗或者觉得讲了半天废话,都一定要往下看,因为这是一个很好的思维锻炼过程。
先休息一下吧
上面相当于讲到了为什么要用ThreaLocal,答案是为了省去传参的麻烦;以及解决思路,那就是把变量定义为静态的。貌似问题都迎刃而解了,再仔细想想发现使用静态变量为什么要用ThreaLocal?自己随便定义一个类都可以用来封装静态变量。因为定义为静态变量就以为着这个变量是线程之间共享的了,那就会线程安全问题,ThreaLocal自然而然就是可以帮助我们方便解决线程安全问题的。
这个时候再去看ThreaLocal原理应该会好理解很多,不然看了一堆博客还是似懂非懂,完全也不知道该怎么用,过几天就会忘记或者搞不清楚那些很绕的原理,面试时一问就懵了。
下面通过一个实例来讲解一下具体用法。比如用户登录之后,用AdminUser类来封装这个用户的所有信息,后续的很多操作都需要用到AdminUser中的信息,但又不想一直传这个参数,那就可以使用ThreaLocal。代码如下
public class AdminHolder {
private static ThreadLocal<AdminUser> userThreadLocal = new ThreadLocal<AdminUser>();
private static ThreadLocal<HttpRequest> requestThreadLocal = new ThreadLocal<HttpRequest>();
// 添加需要共享的变量
public static void addValue(AdminUser adminUser) {
userThreadLocal.set(adminUser);
}
public static void addValue(HttpRequest request) {
requestThreadLocal.set(request);
}
// 获取共享的变量
public static AdminUser getAdminUser() {
return userThreadLocal.get();
}
public static HttpRequest getHttpRequest() {
return requestThreadLocal.get();
}
// 清楚共享的变量
public static void remove() {
userThreadLocal.remove();
requestThreadLocal.remove();
}
}
class AdminUser{
// 有各种成员变量信息
};
class HttpRequest{
// 有各种成员变量信息
};
通过AdminHolder类的get方法就可以获取到想要共享的变量了,那在哪里设置这些变量值呢?像上面这个例子可以定义一个LoginFilter,在里面获取到用户信息和请求信息,然后添加到AdminHolder里。
到目前为止ThreaLocal使用就讲完了,看完这篇文章再去看ThreaLocal原理相信会更好理解。