ThreadLocal与ThreadLocalMap源码分析

该类主要用于不同线程存储自己的线程本地变量。本文先通过一个示例简单介绍该类的使用方法,然后从ThreadLocal类的初始化、存储结构、增删数据和hash值计算等几个方面,分析对应源码。采用的版本为jdk1.8。

ThreadLocal-使用方法

ThreadLocal对象可以在多个线程中被使用,通过set()方法设置线程本地变量,通过get()方法获取设置的线程本地变量。我们先通过一个示例简单了解下使用方法:

public static void main(String[] args){ ThreadLocal<String> threadLocal = new ThreadLocal<>(); // 线程1 new Thread(()->{ // 查看是否有初始值 System.out.println("线程1的初始值:"+threadLocal.get()); // 设置线程1的值 threadLocal.set("V1"); // 输出 System.out.println("线程1的值:"+threadLocal.get()); // 等待一段时间,等线程2设置值后再查看线程1的值 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程1的值:"+threadLocal.get()); }).start(); // 线程2 new Thread(()->{ // 等待线程1设置初始值 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } // 查看线程2的初始值 System.out.println("线程2的值:"+threadLocal.get()); // 设置线程2的值 threadLocal.set("V2"); // 查看线程2的值 System.out.println("线程2的值:"+threadLocal.get()); }).start(); }

由于threadlocal设置的值是在每个线程中都有一个副本的,线程之间不会互相影响。代码运行的结果如下所示:

线程1的初始值:null 线程1的值:V1 线程2的值:null 线程2的值:V2 线程1的值:V1 ThreadLocal-初始化

ThreadLocal类只有一个无参的构造方法,如下所示:

/** * Creates a thread local variable. * @see #withInitial(java.util.function.Supplier) */ public ThreadLocal() { }

但其实还有一个带参数的构造方法,不过是它的子类。ThreadLocal中定义了一个内部类SuppliedThreadLocal,为继承自ThreadLocal类的子类。可以通过该类进行给定初始值的初始化,其定义如下:

static final class SuppliedThreadLocal<T> extends ThreadLocal<T> { private final Supplier<? extends T> supplier; SuppliedThreadLocal(Supplier<? extends T> supplier) { this.supplier = Objects.requireNonNull(supplier); } @Override protected T initialValue() { return supplier.get(); } }

通过TheadLocal threadLocal = Thread.withInitial(supplier);这样的语句可以进行给定初始值的初始化。在某个线程第一次调用get()方法时,会执行initialValue()方法设置线程变量为传入supplier中的值。

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); } ThreadLocal-存储结构

在jdk1.8版本中,使用的是TheadLocalMap这一个容器存储线程本地变量。

该容器的设计思想和HashMap有很多共同之处。比如:内部定义了Entry节点存储键值对(使用ThreadLocal对象作为键);使用一个数组存储entry节点;设定一个阈值,超过阈值时进行扩容;通过键的hash值与数组长度进行&操作确定下标索引等。但也有很多不同之处,具体我们在后续介绍ThreadLocalMap类时再详细分析。

static class ThreadLocalMap { // Entry节点定义 static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } // 存储元素的数组 private Entry[] table; // 容器内元素数量 private int size = 0; // 阈值 private int threshold; // Default to 0 // 修改和添加元素 private void set(ThreadLocal<?> key, Object value){ ... } // 移除元素 private void remove(ThreadLocal<?> key) { ... } ... } ThreadLocal-增删数据

ThreadLocal类提供了get(),set()和remove()方法来操作当前线程的threadlocal变量副本。底层则是基于ThreadLocalMap容器来实现数据操作。

不过要注意的是:ThreadLocal中并没有ThreadLocalMap的成员变量,ThreadLocalMap对象是Thread类中的一个成员,所以需要通过通过当前线程的Thread对象去获取该容器。

每一个线程Thread对象都会有一个map容器,该容器会随着线程的终结而回收。

设置线程本地变量的方法。

public void set(T value) { // 获取当前线程对应的Thread对象,其是map键值对中的健 Thread t = Thread.currentThread(); // 获取当前线程对象的容器map ThreadLocalMap map = getMap(t); // 如果容器不为null,则直接设置元素。否则用线程对象t和value去初始化容器对象 if (map != null) map.set(this, value); else createMap(t, value); } // 通过当前线程的线程对象获取容器 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // 创建map容器,本质是初始化Thread对象的成员变量threadLocals void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzpjdg.html