首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
当前位置:
首页>>
技术小册>>
Java并发编程实战
小册名称:Java并发编程实战
### 30 | 线程本地存储模式:没有共享,就没有伤害 在Java并发编程的广阔领域中,共享资源的同步访问一直是开发者们需要精心处理的难题。线程间的竞争条件、死锁以及性能瓶颈,往往源自于对共享资源的不当管理。为了缓解这些问题,一种高效且直观的策略应运而生——线程本地存储(Thread Local Storage, TLS)模式,其核心思想在于“没有共享,就没有伤害”。本章将深入探讨线程本地存储模式的基本原理、应用场景、实现方式、以及在使用中需要注意的事项。 #### 一、引言:共享资源的挑战 在并发编程中,多个线程可能会同时访问同一个资源,如变量、数据结构或文件等。如果这些访问没有适当的同步机制加以控制,就可能导致数据不一致、脏读、丢失更新等并发问题。虽然Java提供了诸如synchronized关键字、显式锁(如ReentrantLock)、原子变量等多种同步工具,但过度使用或不当使用这些工具可能会引入性能瓶颈,甚至导致死锁。 #### 二、线程本地存储模式概述 线程本地存储模式是一种避免共享资源冲突的有效策略。它通过将每个线程所需的数据存储在自己的独立空间内,从而避免了线程间的直接数据竞争。在Java中,`ThreadLocal`类是实现这一模式的关键工具。每个线程都可以通过`ThreadLocal`实例访问到其自己的、独立的变量副本,这些副本对于其他线程是隔离的。 #### 三、ThreadLocal的工作原理 `ThreadLocal`类内部维护了一个以线程为键(Thread为Key)、以线程局部变量为值(Object为Value)的映射表(ThreadLocalMap)。当线程首次通过`ThreadLocal`实例访问其变量时,如果该线程还没有对应的变量副本,`ThreadLocal`会为其创建一个新的变量副本,并将其存储在映射表中。之后,该线程再次通过相同的`ThreadLocal`实例访问变量时,将直接获取到其自己的变量副本,而不会影响到其他线程。 需要注意的是,虽然`ThreadLocal`提供了线程间的数据隔离,但这也意味着每个线程都会持有自己的一份数据副本,这可能会增加内存的使用量。此外,由于`ThreadLocal`的生命周期与线程的生命周期绑定,如果线程长时间运行或频繁创建销毁,可能会导致内存泄漏的问题。 #### 四、应用场景 1. **用户会话管理**:在Web应用中,每个用户的会话信息(如用户ID、权限等)可以存储在`ThreadLocal`中,以便在当前线程处理的请求过程中方便地访问这些信息,而无需在多个方法间传递这些参数。 2. **数据库连接管理**:在多线程环境下,如果每个线程都需要独立的数据库连接,可以使用`ThreadLocal`来管理这些连接。每个线程都可以通过`ThreadLocal`获取到属于自己的数据库连接,从而避免了连接共享带来的潜在问题。 3. **日志记录**:在复杂的分布式系统中,日志记录是一个重要的功能。通过`ThreadLocal`,可以在每个线程中存储与当前执行路径相关的日志上下文信息(如用户ID、请求ID等),以便于后续的日志分析和问题追踪。 4. **事务管理**:在需要支持事务的应用中,可以使用`ThreadLocal`来存储事务的上下文信息(如事务ID、状态等),以便在事务的不同阶段进行信息的传递和共享。 #### 五、实现方式 在Java中,使用`ThreadLocal`实现线程本地存储非常简单。首先,需要创建一个`ThreadLocal`实例,并通过该实例的`set`方法设置线程局部变量的值。然后,在当前线程中,可以通过该实例的`get`方法获取到该线程自己的变量副本。 ```java import java.util.concurrent.ThreadLocalRandom; public class ThreadLocalExample { private static final ThreadLocal<Integer> randomNumber = ThreadLocal.withInitial(() -> ThreadLocalRandom.current().nextInt(1, 100)); public static void main(String[] args) { Thread thread1 = new Thread(() -> { System.out.println("Thread 1: " + randomNumber.get()); }); Thread thread2 = new Thread(() -> { System.out.println("Thread 2: " + randomNumber.get()); }); thread1.start(); thread2.start(); } } ``` 在上面的例子中,`ThreadLocalRandom.current().nextInt(1, 100)`作为初始值提供器,为每个线程生成了一个1到100之间的随机数,并存储在各自的`ThreadLocal`变量中。因此,即使两个线程几乎同时运行,它们获取的随机数也是不同的。 #### 六、注意事项 1. **内存泄漏**:如前所述,由于`ThreadLocal`的生命周期与线程的生命周期绑定,如果线程长时间运行且不再需要其`ThreadLocal`变量时,应显式调用`remove`方法来清除该线程的变量副本,以避免内存泄漏。 2. **线程池中的使用**:在使用线程池时,由于线程是复用的,因此`ThreadLocal`变量可能会在多个任务之间共享。为了避免这种情况,应在每个任务执行完毕后调用`remove`方法清除变量副本。 3. **性能考虑**:虽然`ThreadLocal`可以避免线程间的数据竞争,但它也会增加内存的使用量。在设计系统时,应权衡其带来的便利性与可能增加的内存开销。 4. **线程安全性**:虽然`ThreadLocal`本身保证了线程间的数据隔离,但存储在`ThreadLocal`中的对象如果被多个线程共享(例如,如果`ThreadLocal`变量存储的是一个可变的共享对象),则仍然需要采取适当的同步措施来确保线程安全。 #### 七、总结 线程本地存储模式是一种在Java并发编程中避免共享资源冲突的有效策略。通过`ThreadLocal`类,我们可以为每个线程提供独立的变量副本,从而避免了线程间的直接数据竞争。然而,在使用`ThreadLocal`时,我们也需要注意其可能带来的内存泄漏问题以及在线程池中的特殊使用场景。通过合理设计和谨慎使用,我们可以充分发挥`ThreadLocal`的优势,为并发应用提供更加健壮和高效的解决方案。
上一篇:
29 | Copy-on-Write模式:不是延时策略的COW
下一篇:
31 | Guarded Suspension模式:等待唤醒机制的规范实现
该分类下的相关小册推荐:
Java必知必会-Maven初级
Java语言基础2-运算符
Java语言基础6-面向对象高级
Java语言基础9-常用API和常见算法
Java语言基础14-枚举和注解
Java语言基础4-数组详解
Java语言基础10-Java中的集合
Mybatis合辑3-Mybatis动态SQL
Java语言基础3-流程控制
JAVA 函数式编程入门与实践
SpringBoot合辑-初级篇
Mybatis合辑5-注解、扩展、SQL构建