ThreadLocal 称为线程本地存储,它为每一个使用它的线程提供一个其值(value)的副本。可以将 ThreadLocal<T> 理解成 Map<Thread, T>,即使用当前线程为 key 的一个 Map,ThreadLocal 的 get() 方法从 Map 里取本地变量(本地 value 对象)。通常对数据库连接(Connection)和事务(Transaction)使用线程本地存储。

      线程池中的线程在任务执行完成后会被复用,所以在线程执行完成时,要对 ThreadLocal 进行清理(清除掉与本线程相关联的 value 对象)。不然,被复用的线程去执行新的任务时会使用被上一个线程操作过的 value 对象,从而产生不符合预期的结果。

      下面举一个简单的例子来说明:

  1. 1 import java.util.concurrent.ExecutorService;
  2. 2 import java.util.concurrent.Executors;
  3. 3
  4. 4 public class ThreadLocalVariableHolder {
  5. 5 private static ThreadLocal<Integer> variableHolder = new ThreadLocal<Integer>() {
  6. 6 @Override
  7. 7 protected Integer initialValue() {
  8. 8 return 0;
  9. 9 }
  10. 10 };
  11. 11
  12. 12 public static int getValue() {
  13. 13 return variableHolder.get();
  14. 14 }
  15. 15
  16. 16 public static void remove() {
  17. 17 variableHolder.remove();
  18. 18 }
  19. 19
  20. 20 public static void increment() {
  21. 21 variableHolder.set(variableHolder.get() + 1);
  22. 22 }
  23. 23
  24. 24 public static void main(String[] args) {
  25. 25 ExecutorService executor = Executors.newCachedThreadPool();
  26. 26 for (int i = 0; i < 5; i++) {
  27. 27 executor.execute(() -> {29 int before = getValue();
  28. 30 increment();
  29. 31 int after = getValue();
  30. 32 System.out.println("before: " + before + ", after: " + after);
  31. 33 });
  32. 34 }
  33. 35
  34. 36 executor.shutdown();
  35. 37 }
  36. 38 }

      执行结果如下(如果你的执行结果 before 都是 0,after 都是 1 的话,就增加线程池执行的线程个数):

  1. before: 0, after: 1
  2. before: 0, after: 1
  3. before: 0, after: 1
  4. before: 1, after: 2
  5. before: 1, after: 2

      既然是为每个线程都提供一个副本,为什么会出现 before 不为 0 的情况呢?

      下面追踪每一个执行的线程,将 main 方法修改为如下:

  1. 1 public static void main(String[] args) {
  2. 2 ExecutorService executor = Executors.newCachedThreadPool();
  3. 3 for (int i = 0; i < 5; i++) {
  4. 4 executor.execute(() -> {
  5. 5 long threadId = Thread.currentThread().getId();
  6. 6 int before = getValue();
  7. 7 increment();
  8. 8 int after = getValue();
  9. 9 System.out.println("threadId: " + threadId + ", before: " + before + ", after: " + after);
  10. 10 });
  11. 11 }
  12. 12
  13. 13 executor.shutdown();
  14. 14 }

      执行结果如下:

  1. threadId: 10, before: 0, after: 1
  2. threadId: 11, before: 0, after: 1
  3. threadId: 12, before: 0, after: 1
  4. threadId: 12, before: 1, after: 2
  5. threadId: 11, before: 1, after: 2

      由上面的执行结果可以看出,id 为 11 和 12 的线程被复用。线程池在复用线程执行任务时使用被之前的线程操作过的 value 对象。因此,在每个线程执行完成时,应该清理 ThreadLocal。具体做法如下:

  1. 1 public static void main(String[] args) {
  2. 2 ExecutorService executor = Executors.newCachedThreadPool();
  3. 3 for (int i = 0; i < 100; i++) {
  4. 4 executor.execute(() -> {
  5. 5 try {
  6. 6 long threadId = Thread.currentThread().getId();
  7. 7 int before = getValue();
  8. 8 increment();
  9. 9 int after = getValue();
  10. 10 System.out.println("threadId: " + threadId + ", before: " + before + ", after: " + after);
  11. 11 } finally {
  12. 12 // 清理线程本地存储
  13. 13 remove();
  14. 14 }
  15. 15 });
  16. 16 }
  17. 17
  18. 18 executor.shutdown();
  19. 19 }

 

版权声明:本文为qifenghao原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/qifenghao/p/8977378.html