为什么需要TreadLocal?

既然ThreadLocal是给每个线程都创建一份变量副本,而这些变量副本之间互不关联,那么为什么不直接在本线程创建变量?还有一个问题,“副本”这个说法是不是本身就不太准确,每个线程都会自行单独的设置值,这些值本身是谁的“副本”,主线程吗?

阅读 3.3k
4 个回答

一点个人见解。

1,你说“那么为什么不直接在本线程创建变量?”

没错的,如果你需要的只是一个局部变量,那就写个局部变量就行,不用太考虑 ThreadLocal。

ThreadLocal 应对的场景是你想要让所有线程用相同的方式访问,而又不互相影响。比如这个爆栈的回答就比较好

像是我们写 web 应用的时候,有很多数据是和请求/会话绑定的,比如当前请求的用户信息,来源,请求数据,你可以在每个调用里传这些参数(也就是你说的,我可以写一个局部变量来实现):

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  doSomething(user)
  doSomethingElse(user)
  renderResponse(resp,user)
}

又或者,稍微灵活一点,不用在请求绑定的数据出现变化时动函数签名,也不用改 request 或者别的什么类的字段,只要把所有和请求绑定的数据都存进 ThreadLocal 就好了。

doGet(HttpServletRequest req, HttpServletResponse resp) {
  User user = getLoggedInUser(req);
  StaticClass.getThreadLocal().set(user)
  try {
    doSomething()
    doSomethingElse()
    renderResponse(resp)
  }
  finally {
    StaticClass.getThreadLocal().remove()
  }
}

很多时候 ThreadLocal 并不是非他不可,只是另一种可能更漂亮、实用的选择。

2,还有一个问题,“副本”这个说法是不是本身就不太准确,每个线程都会自行单独的设置值,这些值本身是谁的“副本”,主线程吗?

本质上,ThreadLocal 就是个 Map<线程ID,你的数据>,‘副本’这个词表达的是每个线程都有自己的数据,这是站在全局的角度(每个线程都有自己的数据)而不是单个线程的角度(我读写我的数据)看的。

所以‘副本’不能理解为‘是某个对象的复制品’,更像是‘一个线程一个坑’。

如果能够用完即扔,线程隔离当然是个更好的方式,而且还没有状态,绝对不会对其他线程产生影响。但线程的生民周期超长,如果不能统一管理变量,很容易就内存泄漏了,针对编码来说,就更加困难。其实你使用ThreadLocal ,它依然时使用的 Thread.threadLocals 这也是一个 Map,是和线程绑定的。和你说的也没差,从某种意义上来说,它使用的依然是线程隔离技术,而且还额外提供了更强大的功能。至于副本一说,我是有点摸不着头脑,ThreadLocal 只有在使用的时候会为thread 创建一个ThreadLocalMap,这一点对thread 增加了额外的负担,只能在线程退出时才能清理。但它的key是用的软引用,而且每次操作都会检查 陈旧项目,已经尽可能的把负担降到最低,一般的程序员也写不出这么好的代码。

结合@weak_ptr 的答案看一下。
前端用户点击按钮,调用controller,后续经过一系列的调用栈,执行完毕后返回到前台。
这个周期是一个线程的生命周期。
如果你想在这一系列的调用栈里随时获取用户信息,在调用controller里你可以通过session,但是在service层是获取不到的。如果你想在这一个线程的生命周期内随便取用用户信息,要么你就得每个方法传几十个形参,要么你就用一个全局变量。同时你还不能让这个全局变量让别的线程改了,那么你就需要一个线程内的全局变量,这就是ThreadLocal。

为什么不直接在本线程创建变量

请问如何创建线程内的全局变量?

静态变量是全局的,不是线程有效的。所以pass。
通过传参给每个方法??
定义个map<线程id,变量值>??这不就是ThreadLocal嘛。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题