在JDK的早期版本中,提供了一种解决多线程并发问题的方案:java.lang.ThreadLocal类。ThreadLocal类在维护变量时,实际使用了当前线程(Thread)中的一个叫做ThreadLocalMap的独立副本,每个线程可以独立修改属于自己的副本而不会互相影响,从而隔离了线程和线程,避免了线程访问实例变量发生冲突的问题。

  ThreadLocal本身并不是一个线程,而是通过操作当前线程中的一个内部变量来达到与其他线程隔离的目的。之所以取名为ThreadLocal,所期望表达的含义是其操作的对象是线程的一个本地变量。

Thread.java

public class Thread implements Runnable {
// 这里省略了许多其他的代码
ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocal.java

public class ThreadLocal<T> {
// 这里省略了许多其他代码
// 将value 的值保存于当前线程的本地变量中
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 调用getMap 方法获得当前线程中的本地变量ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果ThreadLocalMap 已存在,直接使用
if (map != null)
// 以当前的ThreadLocal 的实例作为key,存储于当前线程的
// ThreadLocalMap 中,如果当前线程中定义了多个不同的ThreadLocal
// 的实例,则它们会作为不同key 进行存储而不会互相干扰
map.set(this, value);
else
// 如果ThreadLocalMap 不存在,则为当前线程创建一个新的
createMap(t, value);
} // 获取当前线程中以当前ThreadLocal 实例为key 的变量值
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程中的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取当前线程中以当前ThreadLocal 实例为key 的变量值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T) e.value;
}
// 当map 不存在时,设置初始值
return setInitialValue();
} // 从当前线程中获取与之对应的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
} // 创建当前线程中的ThreadLocalMap
void createMap(Thread t, T firstValue) {
// 调用构造函数生成当前线程中的ThreadLocalMap
t.threadLocals = new ThreadLocalMap(this, firstValue);
} // ThreadLoaclMap 的定义
static class ThreadLocalMap {
//这里省略了许多代码
}
}
  • ThreadLocalMap变量属于线程的内部属性,不同的线程拥有完全不同的ThreadLo-calMap变量。
  • 线程中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或者get操作时创建的。
  • 在创建ThreadLocalMap之前,会首先检查当前线程中的ThreadLocalMap变量是否已经存在,如果不存在则创建一个;如果已经存在,则使用当前线程已创建的ThreadLo-calMap。
  • 使用当前线程的ThreadLocalMap的关键在于使用当前的ThreadLocal的实例作为key进行存储。

ThreadLocal模式至少从两个方面完成了数据访问隔离,即横向隔离和纵向隔离。

  • 纵向隔离——线程与线程之间的数据访问隔离。这一点由线程的数据结构保证。因为每个线程在进行对象访问时,访问的都是各个线程自己的ThreadLocalMap。
  • 横向隔离——同一个线程中,不同的Thread-Local实例操作的对象之间相互隔离。这一点由ThreadLocalMap在存储时采用当前ThreadLocal的实例作为key来保证。

深入比较ThreadLocal模式与synchronized关键字

  • ThreadLocal是一个Java类,通过对当前线程中的局部变量的操作来解决不同线程的变量访问的冲突问题。所以,ThreadLocal提供了线程安全的共享对象机制,每个线程都拥有其副本。
  • Java中的synchronized是一个保留字,它依靠JVM的锁机制来实现临界区的函数或者变量在访问中的原子性。在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。此时,被用作“锁机制”的变量是多个线程共享的。
  • 同步机制(synchronized关键字)采用了“以时间换空间”的方式,提供一份变量,让不同的线程排队访问。而ThreadLocal采用了“以空间换时间”的方式,为每一个线程都提供一份变量的副本,从而实现同时访问而互不影响。

要完成ThreadLocal模式,其中最关键的地方就是创建一个任何地方都可以访问到的ThreadLocal实例。而这一点,我们可以通过类变量来实现,这个用于承载类变量的类就被视作是一个共享环境。

public class Counter {
// 新建一个静态的ThreadLocal 变量,并通过get 方法将其变为一个可访问的对象
private static ThreadLocal<Integer> counterContext = new
ThreadLocal<Integer>() {
protected synchronized Integer initialValue() {
return 10;
}
}; // 通过静态的get 方法访问ThreadLocal 中存储的值
public static Integer get() {
return counterContext.get();
} // 通过静态的set 方法将变量值设置到ThreadLocal 中
public static void set(Integer value) {
counterContext.set(value);
} // 封装业务逻辑,操作存储于ThreadLocal 中的变量
public static Integer getNextCounter() {
counterContext.set(counterContext.get() + 1);
return counterContext.get();
}
}
public class ThreadLocalTest extends Thread {
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("Thread[" + Thread.currentThread().getName() + "],counter=" + Counter.getNextCounter());
}
}
}
public class Test {
public static void main(String[] args) throws Exception {
ThreadLocalTest testThread1 = new ThreadLocalTest();
ThreadLocalTest testThread2 = new ThreadLocalTest();
ThreadLocalTest testThread3 = new ThreadLocalTest();
testThread1.start();
testThread2.start();
testThread3.start();
}
}

我们来运行一下上面的代码,并看看输出结果:

ThreadLocal模式最合适的使用场景:在同一个线程的不同开发层次中共享数据。

ThreadLocal模式的两个主要步骤:

  • 建立一个类,并在其中封装一个静态的ThreadLocal变量,使其成为一个共享数据环境。
  • 在类中实现访问静态ThreadLocal变量的静态方法(设值和取值)。

未完待续...

05-06 11:34