TheadLocal类学习
ThreadLocal
是 Java 中一个非常实用的线程相关的类,它提供线程本地变量,即每个线程都有自己独立的变量副本,从而避免了线程安全问题。下面我将通过几个方面来帮助你理解并学习如何使用 ThreadLocal
。
基本概念
- 线程局部变量:每个线程都拥有一份
ThreadLocal
变量的副本,彼此之间互不影响,这使得在多线程环境下可以做到数据隔离,提高程序的并发能力。 - 应用场景:适用于每个线程需要独立的资源或状态的情况,比如数据库连接、事务管理、线程专属的缓存、记录日志时的线程ID等。
使用步骤
- 创建
ThreadLocal
实例:指定泛型类型,表示存储在其中的变量类型。 - 设置值:通过
set
方法为当前线程设置值。 - 获取值:通过
get
方法获取当前线程对应的值。 - 清理:使用完毕后,可以通过
remove
方法清理当前线程的值,避免内存泄漏。
例:应用:如何使用ThreadLocal为每个线程创建独立的计数器
1 import java.util.concurrent.ExecutorService; 2 import java.util.concurrent.Executors; 3 4 5 /** 6 * 在这个示例中,尽管使用了ThreadLocal来存储每个线程的计数,但由于线程池中的线程会被复用, 7 * 当一个线程完成一个任务并开始处理下一个任务时,之前设置在ThreadLocal中的计数值没有被清除。 8 * 9 * 因此,第二个任务开始时,它可能会继承上一个任务在该线程中设置的计数,导致数据看起来像是“交叉污染”。 10 */ 11 public class ThreadLocalMisuseExample { 12 13 // 静态ThreadLocal变量,用于存储当前线程的计数 14 private static final ThreadLocal<Integer> threadCounter = ThreadLocal.withInitial(() -> 0); // 或 new ThreadLocal<>();但是为初始化为0 15 16 public static void main(String[] args) { 17 ExecutorService executor = Executors.newFixedThreadPool(2); // 创建固定大小的线程池 18 19 // 提交任务到线程池 20 for (int i = 0; i < 4; i++) { 21 int taskId = i; 22 executor.submit(() -> { 23 Integer count = threadCounter.get(); 24 System.out.println("Task ID " + taskId + " on thread " + Thread.currentThread().getName() 25 + ", initial count: " + count); 26 threadCounter.set(count + 1); // 增加计数 27 System.out.println("Task ID " + taskId + " on thread " + Thread.currentThread().getName() 28 + ", updated count: " + threadCounter.get()); 29 30 // 修正交叉污染问题:清除ThreadLocal变量,防止数据污染 31 // threadCounter.remove(); 32 }); 33 } 34 35 executor.shutdown(); 36 } 37 }
结果1:
Task ID 1 on thread pool-1-thread-2, initial count: 0 Task ID 0 on thread pool-1-thread-1, initial count: 0 Task ID 1 on thread pool-1-thread-2, updated count: 1 Task ID 0 on thread pool-1-thread-1, updated count: 1 Task ID 2 on thread pool-1-thread-1, initial count: 1 Task ID 2 on thread pool-1-thread-1, updated count: 2 Task ID 3 on thread pool-1-thread-2, initial count: 1 Task ID 3 on thread pool-1-thread-2, updated count: 2
结果2: 即把31行 注释放开 // threadCounter.remove();
--修正后,每个Task的两个thread计数:initial为0、updated为1
Task ID 0 on thread pool-1-thread-1, initial count: 0 Task ID 1 on thread pool-1-thread-2, initial count: 0 Task ID 1 on thread pool-1-thread-2, updated count: 1 Task ID 0 on thread pool-1-thread-1, updated count: 1 Task ID 2 on thread pool-1-thread-2, initial count: 0 Task ID 3 on thread pool-1-thread-1, initial count: 0 Task ID 3 on thread pool-1-thread-1, updated count: 1 Task ID 2 on thread pool-1-thread-2, updated count: 1
例2:其它使用ThreadLocal导致的串号问题:如 Server通常使用ThreadPool来接受处理请求,而ThreadPool中的Thread是复用的。