为什么在 Java 中需要使用 ThreadLocal?
为什么在 Java 中需要使用 ThreadLocal?
回答重点
因为在多线程编程中,多个线程可能会同时访问和修改共享变量,导致线程安全问题。ThreadLocal 提供了一种简单的解决方案,使每个线程都有自己的独立变量副本,避免了多线程间的变量共享和竞争,从而解决了线程安全问题。
与通过加锁、同步块等传统方式来保证线程安全相比。ThreadLocal 不需要对变量访问进行同步,减少了上下文切换、锁竞争的性能损耗。
扩展知识
常见应用场景
- 数据库连接管理:每个线程拥有自己的数据库连接,避免了多个线程共享同一个连接导致的线程安全问题。
- 用户上下文管理:在处理用户请求时,每个线程拥有独立的用户上下文(如用户ID、Session信息),在并发环境中确保正确的用户数据。
ThreadLocal 的原理
ThreadLocal 通过为每个线程创建一个独立的变量副本来实现线程本地化存储。ThreadLocal 实际上是为每个线程创建了一个 ThreadLocalMap,而 ThreadLocalMap 是每个线程内部持有的结构。
该 ThreadLocalMap 的键是 ThreadLocal 对象,而值则是线程独立的变量副本。当线程访问 ThreadLocal.get() 时,它会根据当前线程在自己的 ThreadLocalMap 中找到对应的变量副本。
以下是一个简化的访问流程:
- 线程A访问
ThreadLocal.get()时,从ThreadLocalMap中找到与该ThreadLocal对象对应的值。 - 线程B访问
ThreadLocal.get()时,它有自己独立的ThreadLocalMap,获取的是与其自身相关的值,互不干扰。
ThreadLocal 通俗理解
最近不是开放三胎政策嘛,假设你有三个孩子。
现在你带着三个孩子出去逛街,路过了玩具店,三个孩子都看中了一款变形金刚。
所以你买了一个变形金刚,打算让三个孩子轮着玩。
回到家你发现,孩子因为这个玩具吵架了,三个都争着要玩,谁也不让着谁。
这时候怎么办呢?你可以去拉架,去讲道理,说服孩子轮流玩,但这很累。
所以一个简单的办法就是出去再买两个变形金刚,这样三个孩子都有各自的变形金刚,世界就暂时得到了安宁。
映射到我们今天的主题,变形金刚就是共享变量,孩子就是程序运行的线程。有多个线程(孩子),争抢同一个共享变量(玩具),就会产生冲突,而程序的解决办法是加锁(父母说服,讲道理,轮流玩),但加锁就意味着性能的消耗(父母比较累)。
所以有一种解决办法就是避免共享(让每个孩子都各自拥有一个变形金刚),这样线程之间就不需要竞争共享变量(孩子之间就不会争抢)。
所以为什么需要 ThreadLocal?
就是为了通过本地化资源来避免共享,避免了多线程竞争导致的锁等消耗。
这里需要强调一下,不是说任何东西都能直接通过避免共享来解决,因为有些时候就必须共享。
举个例子:当利用多线程同时累加一个变量的时候,此时就必须共享,因为一个线程的对变量的修改需要影响要另个线程,不然累加的结果就不对了。
再举个不需要共享的例子:比如现在每个线程需要判断当前请求的用户来进行权限判断,那这个用户信息其实就不需要共享,因为每个线程只需要管自己当前执行操作的用户信息,跟别的用户不需要有交集。
好了,道理很简单,这下子想必你已经清晰了 ThreadLocal 出现的缘由了。
代码示例
1 | public class ThreadLocalExample { |
在上面的例子中,三个线程各自操作 ThreadLocal 提供的变量副本,互不干扰,解决了多线程之间的共享数据竞争问题。