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<Integer>,然后启动了10个线程对threadLocal进行set和get操作, 通过下面的输出可以发现, 这10个线程之间彼此不会相互影响, 每一个线程存入thread Local中的i值也是完全不同彼此独立的。

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的数据项清理工作。

更多推荐