1.什么是上下文

上下文是贯穿整个系统或阶段生命周期的对象,其中包含了系统全局的一些信息,比如登录之后的用户信息、账号信息,以及在程序每一个阶段运行时的数据。

典型的使用单例对象充当系统级别上下文的例子:

2.线程上下文设计

在有些时候,单个线程执行的任务步骤会非常多,后一个步骤的输人有可能是前一个步骤的输出,比如在单个线程多步骤(阶段)执行时,为了使得功能单一,有时候我们会采用GoF职责链设计模式, 如图所示:
image.png

3.ThreadLocal详解

自JDK1.2版本起, Java就提供了java.lang.ThreadLocal, ThreadLocal为每一个使用该变量的线程都提供了独立的副本,可以做到线程间的数据隔离,每一个线程都可以访问各自内部的副本变量。

3.1 ThreadLocal的使用场景及注意事项

Thread Local在Java的开发中非常常见, 一般在以下情况中会使用到Thread Local。

  • 在进行对象跨层传递的时候, 可以考虑使用Thread Local, 避免方法多次传递, 打破层次间的约束。
  • 线程间数据隔离
  • 进行事务操作,用于存储线程事务信息。

3.2 ThreadLocal的方法详解及源码分析

先看一下简单的ThreadLocal简单示例

  1. import java.util.concurrent.TimeUnit;
  2. import java.util.stream.IntStream;
  3. public class ThreadLocalExample {
  4. public static void main(String[] args) {
  5. // 创建ThreadLocal实例
  6. ThreadLocal<Integer> tlocal = new ThreadLocal<>();
  7. // 创建十个线程使用tlocal
  8. IntStream.range(0,10).forEach(i->new Thread(
  9. () -> {
  10. try {
  11. tlocal.set(i);
  12. System.out.println(Thread.currentThread() + " set i " + tlocal.get());
  13. TimeUnit.SECONDS.sleep(1);
  14. System.out.println(Thread.currentThread() + " get i " + tlocal.get());
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. ).start() );
  20. }
  21. }

上面的代码中定义了一个全局唯一的ThreadLocal,然后启动了10个线程对threadLocal进行set和get操作, 通过下面的输出可以发现, 这10个线程之间彼此不会相互影响, 每一个线程存入thread Local中的i值也是完全不同彼此独立的。

  1. Connected to the target VM, address: '127.0.0.1:64928', transport: 'socket'
  2. Thread[Thread-1,5,main] set i 1
  3. Thread[Thread-0,5,main] set i 0
  4. Thread[Thread-6,5,main] set i 6
  5. Thread[Thread-3,5,main] set i 3
  6. Thread[Thread-4,5,main] set i 4
  7. Thread[Thread-5,5,main] set i 5
  8. Thread[Thread-8,5,main] set i 8
  9. Thread[Thread-9,5,main] set i 9
  10. Thread[Thread-2,5,main] set i 2
  11. Thread[Thread-7,5,main] set i 7
  12. Thread[Thread-6,5,main] get i 6
  13. Thread[Thread-1,5,main] get i 1
  14. Thread[Thread-0,5,main] get i 0
  15. Thread[Thread-3,5,main] get i 3
  16. Thread[Thread-8,5,main] get i 8
  17. Thread[Thread-5,5,main] get i 5
  18. Thread[Thread-9,5,main] get i 9
  19. Thread[Thread-7,5,main] get i 7
  20. Thread[Thread-4,5,main] get i 4
  21. Thread[Thread-2,5,main] get i 2
  22. Disconnected from the target VM, address: '127.0.0.1:64928', transport: 'socket'

在使用Thread Local的时候, 最常用的方法就是initialValue() 、set(Tt) 、get() 。

(1) initialValue() 方法

initialValue() 方法为Thread Local要保存的数据类型指定了一个初始化值, 在ThreadLocal中默认返回值为null,示例代码如下:

  1. protected T initialValue() {
  2. return null;
  3. }

(2) set(Tt) 方法

set方法主要是为Thread Local指定将要被存储的数据, 如果重写了initialValue() 方法,在不调用set(Tt) 方法的时候, 数据的初始值是initialValue() 方法的计算结果, 示例代码如下:

ThreadLocal 的 set源码

  1. public void set(T value) {
  2. Thread t = Thread.currentThread();
  3. ThreadLocalMap map = getMap(t);
  4. if (map != null)
  5. map.set(this, value);
  6. else
  7. createMap(t, value);
  8. }

createMap源码

  1. void createMap(Thread t, T firstValue) {
  2. t.threadLocals = new ThreadLocalMap(this, firstValue);
  3. }

set方法

  1. private void set(ThreadLocal<?> key, Object value) {
  2. // We don't use a fast path as with get() because it is at
  3. // least as common to use set() to create new entries as
  4. // it is to replace existing ones, in which case, a fast
  5. // path would fail more often than not.
  6. Entry[] tab = table;
  7. int len = tab.length;
  8. int i = key.threadLocalHashCode & (len-1);
  9. for (Entry e = tab[i];
  10. e != null;
  11. e = tab[i = nextIndex(i, len)]) {
  12. ThreadLocal<?> k = e.get();
  13. if (k == key) {
  14. e.value = value;
  15. return;
  16. }
  17. if (k == null) {
  18. replaceStaleEntry(key, value, i);
  19. return;
  20. }
  21. }
  22. tab[i] = new Entry(key, value);
  23. int sz = ++size;
  24. if (!cleanSomeSlots(i, sz) && sz >= threshold)
  25. rehash();
  26. }

上述代码的运行步骤具体如下。
1) 获取当前线程Thread.current Thread() 。
2) 根据当前线程获取与之关联的Thread Local Map数据结构。
3) 如果map为null则进入第4步, 否则进入第5步。
4) 当map为null的时候创建一个Thread Local Map, 用当前Thread Local实例作为key, 将要存放的数据作为Value, 对应到Thread Local Map中则是创建了一个Entry。
5) 在map的set方法中遍历整个map的Entry, 如果发现Thread Local相同, 则使用新的数据替换即可, set过程结束。
6) 在遍历map的entry过程中, 如果发现有Entry的Key值为null, 则直接将其逐出并且使用新的数据占用被逐出数据的位置,这个过程主要是为了防止内存泄漏(关于Thread Local的内存泄漏在21.3.3节中有详细介绍) 。
7) 创建新的entry, 使用Thread Local作为Key, 将要存放的数据作为Value。
8) 最后再根据Thread Local Map的当前数据元素的大小和阀值做比较, 再次进行key为null的数据项清理工作。

(3) get() 方法

get用于返回当前线程在ThreadLocal中的数据备份, 当前线程的数据都存放在一个称为ThreadLocalMap的数据结构中, 我们稍后会介绍ThreadLocalMap, get方法示例代码如下: