并发杂谈之谈谈ThreadLocal

215 阅读3分钟

最近在找实习,面试中碰到了不少面试官问并发的问题。浅的有问到进程、线程、锁,深的有问到缓存一致性、内存屏障等……

本着只输入不输出能学到的东西始终有限的想法,我决定详细的整理一遍并发相关的内容,写成一份《并发杂谈》系列文章。不会涉及太多高级语言层面的东西,更多是偏os的底层分享。

最后,我选择 ThreadLcoal 作为该系列文章的第一篇文章,因为我很久没有写技术类型的博客文章了,而 ThreadLocal 内容较为简单,正好帮我热热身悉,找一下写技术博客的状态。

总结写在前面

ThreadLocal和锁一样,都是处理并发安全的机制。但是不同于锁,ThreadLocal是一种空间换时间的机制,它在线程本地栈区复制一份堆区的原始数据,后续线程对该数据的操作都会映射到本地栈区的操作,从而避免了多线程对该变量的竞争。

由于ThreadLocal将变量缓存到了栈区且不会同步回堆区,所以线程之间的数据是相互隔离的。如果我们需要线程协作,那ThreadLocal并不适用,还是老老实实用锁吧。

基于这个特性,我们可以通过ThreadLocal实现以下功能:

  • 线程之间的数据隔离。
  • 不同的线程通过同一个变量访问到不同的数据(如连接不同的数据库、保存不同的会话数据),实现一个类似“多态”的效果。

1. 为什么要 ThreadLocal?

当一个进程无法满足人们对于性能的需求时,人们从进程身上进一步设计了线程。

进程存在的性能问题:当进程中存在某个任务需要调用阻塞的系统调用(如 I/O)时,整个进程会陷入阻塞,影响进程其余模块的正常运行。

进程作为资源分配的基本单位,线程作为 os 调度的基本单位。 站在内存角度,进程拥有一片属于自己的内存,而这片内存又被该进程拥有的若干线程共享着。 线程将这片内存进一步划分,分出了自己私有的栈区和与其他线程共享的堆区

image.png

由于堆区是共享的,线程在并发地对堆区的数据做操作时,很可能干扰到其他线程。 所以我们需要一些机制来帮我们保证线程并发安全。

主流的策略是“锁”。 锁是一个优秀的解决方案,可以完美避免线程并发导致的数据不安全问题。但是,还是那句话,有利就有弊,锁对系统性能有很大的影响。最坏的情况下,并发程序会硬生生变成串行程序。所以锁不能滥用。

而另一种机制就是ThreadLocal,这是一种空间换时间的方法。

2. ThreadLocal具体是怎么做的?

ThreadLocal是一种空间换时间的方法。 如上文所示,线程拥有自己的私有栈区,其他线程无法访问。 ThreadLocal将数据保存在这个栈区,便可以隔离开其他线程,避免数据竞争。

image.png

基于这个特性,我们可以通过ThreadLocal实现以下功能:

  • 线程之间的数据隔离。
  • 不同的线程通过同一个变量访问到不同的数据(如连接不同的数据库、保存不同的会话数据),实现一个类似“多态”的效果。