我们知道,进程是OS分配资源的最小单位,而线程是执行操作的最小单位并不享有资源。ThreadLocal实现了线程数据变量的本地存储,每个线程都存储自己的变量,所有的线程都使用同样的ThreadLocal<T>对象来存取变量,但是每个线程在存取时看到的变量值是不同的,不会影响到其他线程的变量,并且值可以为null。
整个实现架构图如下:
一个线程可以持有多个ThreadLocal对象,每个ThreadLocal实例其实很轻量级,只保存hashCounter分配给它的hash值和自身的一个弱引用。存取时只要将values里的obj数组复制当前方法的局部变量中操作就可以了。
原来的JDK中,table是用map实现的;在1.7源码中使用了object数组来存放<ThreadLocal,T>,如图间隔存放了kv;这样做避开了线程并发的锁操作,大大加快了存取的速度。另外值得提一点的是,hashCounter每次分配给ThreadLocal对象的hash值都是偶数,这样取得的index位置来存放ThreadLocal对象,index+1位置存放变量值,十分巧妙。
ThreadLocal类结构如下,我们接下来依次分析这些方法:
- Values 静态内部类,相当于一个全局变量,里面维护了一个object数组,使每个线程都可以访问它。
- public ThreadLocal() {}构造方法为空
- public T get() 返回了当前线程中存储的变量值
- protected T initialValue() 返回null,根据需要重写该方法
- public void set(T value) 设置当前线程中存储的变量值
- public void remove() 删除当前线程中存储的变量值
- Values initializeValues(Thread current) 创建当前进程对应的values对象
- Values values(Thread current) 获取当前进程的values实例
介绍完了整个架构,我们先不去看values这个静态内部类,其实它维护了ThreadLocal到变量值的映射,一个hashtable而已,回头再来看它。
先来看一眼完成当前线程变量值的get方法:
public T get() { // Optimized for the fast path. Thread currentThread = Thread.currentThread();//获取到当前线程实例 Values values = values(currentThread);//获取到当前线程对应的values实例 if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread);//如果当前线程对应的values为空,就新建一个 } return (T) values.getAfterMiss(this); }
方法中,根据当前线程实例获取到values,先来看看如果values为空会如何?
如果values为空则对其初始化,调用initializeValues方法:
/** * Creates Values instance for this thread and variable type. */ Values initializeValues(Thread current) { return current.localValues = new Values();//new一个values实例 }
我们来看一下Values的构造方法:
Values() { initializeTable(INITIAL_SIZE);//INITIAL_SIZE为16 this.size = 0;//table中存储的键值对entry数目 this.tombstones = 0;//废弃的entry数目 }
通过initializeTable方法来创建一个object数组,容量为32,mask值为0x1F。
private void initializeTable(int capacity) { this.table = new Object[capacity * 2];//通过给定的初始化容量创建table,一个obj数组 this.mask = table.length - 1;//之前capacity规定必须为2的幂,这里length默认为31, this.clean = 0; this.maximumLoad = capacity * 2 / 3; // 2/3 最大负载因子 }
我们重新回到get方法中,方法最后返回getAfterMiss(this),该方法将当前ThreadLocal传入,并返回initialValue()定义的值,这个方法是可以自定义重写的。
如果values不为空,我们将副本table复制到当前方法变量中进行操作,由于每个ThreadLocal对象都有固定的hash值,所以不存在线程并发的问题。
ThreadLocal中其他的操作方法也是这样。操作完成后,我们需要与Values中的数组交互,这里就调用了put方法:
/** * Sets entry for given ThreadLocal to given value, creating an * entry if necessary. */ void put(ThreadLocal<?> key, Object value) { cleanUp();//先清理了废弃的元素 // Keep track of first tombstone. That's where we want to go back // and add an entry if necessary. int firstTombstone = -1; for (int index = key.hash & mask;; index = next(index)) { Object k = table[index]; if (k == key.reference) { // Replace existing entry. table[index + 1] = value; return; } if (k == null) { if (firstTombstone == -1) { // Fill in null slot. table[index] = key.reference; table[index + 1] = value; size++; return; } // Go back and replace first tombstone. table[firstTombstone] = key.reference; table[firstTombstone + 1] = value; tombstones--; size++; return; } // Remember first tombstone. if (firstTombstone == -1 && k == TOMBSTONE) { firstTombstone = index; } } }
每次操作object数组,都要先清理一下废弃的元素。然后再进行元素存放。
总结
ThreadLocal与values的组合设计实现了多个线程存储本地变量而又互不干扰的功能,更令人叫绝的是通过固定hash值分配的方式,避开了锁操作。关于Values内部的object数组的维护比较复杂,以后有机会再来研究补充。