在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 { //这里省略了许多代码 } }
ThreadLocal模式至少从两个方面完成了数据访问隔离,即横向隔离和纵向隔离。
深入比较ThreadLocal模式与synchronized关键字
要完成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(); } }
我们来运行一下上面的代码,并看看输出结果:
Thread[Thread-2],counter=11Thread[Thread-2],counter=12Thread[Thread-2],counter=13Thread[Thread-0],counter=11Thread[Thread-0],counter=12Thread[Thread-0],counter=13Thread[Thread-1],counter=11Thread[Thread-1],counter=12Thread[Thread-1],counter=13
ThreadLocal模式最合适的使用场景:在同一个线程的不同开发层次中共享数据。
ThreadLocal模式的两个主要步骤:
未完待续...