1.什么是上下文
上下文是贯穿整个系统或阶段生命周期的对象,其中包含了系统全局的一些信息,比如登录之后的用户信息、账号信息,以及在程序每一个阶段运行时的数据。
典型的使用单例对象充当系统级别上下文的例子:
2.线程上下文设计
在有些时候,单个线程执行的任务步骤会非常多,后一个步骤的输人有可能是前一个步骤的输出,比如在单个线程多步骤(阶段)执行时,为了使得功能单一,有时候我们会采用GoF职责链设计模式, 如图所示:
3.ThreadLocal详解
自JDK1.2版本起, Java就提供了java.lang.ThreadLocal, ThreadLocal为每一个使用该变量的线程都提供了独立的副本,可以做到线程间的数据隔离,每一个线程都可以访问各自内部的副本变量。
3.1 ThreadLocal的使用场景及注意事项
Thread Local在Java的开发中非常常见, 一般在以下情况中会使用到Thread Local。
- 在进行对象跨层传递的时候, 可以考虑使用Thread Local, 避免方法多次传递, 打破层次间的约束。
- 线程间数据隔离
- 进行事务操作,用于存储线程事务信息。
3.2 ThreadLocal的方法详解及源码分析
先看一下简单的ThreadLocal简单示例
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class ThreadLocalExample {
public static void main(String[] args) {
// 创建ThreadLocal实例
ThreadLocal<Integer> tlocal = new ThreadLocal<>();
// 创建十个线程使用tlocal
IntStream.range(0,10).forEach(i->new Thread(
() -> {
try {
tlocal.set(i);
System.out.println(Thread.currentThread() + " set i " + tlocal.get());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread() + " get i " + tlocal.get());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
).start() );
}
}
上面的代码中定义了一个全局唯一的ThreadLocal
Connected to the target VM, address: '127.0.0.1:64928', transport: 'socket'
Thread[Thread-1,5,main] set i 1
Thread[Thread-0,5,main] set i 0
Thread[Thread-6,5,main] set i 6
Thread[Thread-3,5,main] set i 3
Thread[Thread-4,5,main] set i 4
Thread[Thread-5,5,main] set i 5
Thread[Thread-8,5,main] set i 8
Thread[Thread-9,5,main] set i 9
Thread[Thread-2,5,main] set i 2
Thread[Thread-7,5,main] set i 7
Thread[Thread-6,5,main] get i 6
Thread[Thread-1,5,main] get i 1
Thread[Thread-0,5,main] get i 0
Thread[Thread-3,5,main] get i 3
Thread[Thread-8,5,main] get i 8
Thread[Thread-5,5,main] get i 5
Thread[Thread-9,5,main] get i 9
Thread[Thread-7,5,main] get i 7
Thread[Thread-4,5,main] get i 4
Thread[Thread-2,5,main] get i 2
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,示例代码如下:
protected T initialValue() {
return null;
}
(2) set(Tt) 方法
set方法主要是为Thread Local指定将要被存储的数据, 如果重写了initialValue() 方法,在不调用set(Tt) 方法的时候, 数据的初始值是initialValue() 方法的计算结果, 示例代码如下:
ThreadLocal 的 set源码
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
createMap源码
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set方法
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
上述代码的运行步骤具体如下。
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方法示例代码如下: