问题
如果一段代码中所需要的数据必须与其他代码共享,那么就需要看这些共享数据的代码能否在同一个线程中执行,如果能保证,那么我们就可以把共享数据的可见范围限制在同一线程之内,这样无须同步也能保证线程之间不出现数据的争用问题。
实现
在java中可以通过java.lang.ThreadLocal类来实现线程本地存储的功能。每个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以TreadLocal.threadLocalHashCode为键,以本地线程变量为值的K-V值对,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在线程K-V值对中找到对应的本地线程变量。
源码分析
测试代码
public class TestThreadLocal {
private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new TestThread(i)).start();
}
}
static class TestThread implements Runnable {
private int index;
public TestThread(int index) {
this.index = index;
}
public void run() {
System.out.println("线程" + index + "的累加前:" + value.get());
for (int i = 0; i < 5; i++) {
value.set(value.get() + i);
}
System.out.println("线程" + index + "的累加后:" + value.get());
}
}
}
结果
从上面的代码运行结果可以看出,各个线程的value值是相互独立的,本线程的累加操作不会影响到其他线程的值。
如何实现呢
这是JDK1.8版本的ThreadLocal的get()方法
public T get() {
//获取到当前线程
Thread t = Thread.currentThread();
//以当前线程为key拿到该线程自己的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//根据ThreadLocal.threadLocalHashCode拿到Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//Entry不为空,则返回value
T result = (T)e.value;
return result;
}
}
//ThreadLocalMap 为空或者e为空,则通过initialValue函数获取初始值value,
//然后用ThreadLocal的引用和value作为key和value创建一个新的Map
return setInitialValue();
}
//getEntry()方法。根据ThreadLocal.threadLocalHashCode拿到Entry
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
//setInitialValue()方法。ThreadLocalMap 为空或者e为空,则通过initialValue函数获取初始值value,
//然后用ThreadLocal的引用和value作为key和value创建一个新的Map
private T setInitialValue() {
//value默认是NULL
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
以上就是get()方法的流程:
① 获取当前线程
②然后根据当前线程获取ThreadLocalMap
③如果获取的ThreadLocalMap不为空,则在ThreadLocalMap中以ThreadLocal.threadLocalHashCode为key拿到Entry,最后获取到value。如果ThreadLocalMap为空或者e为空,则转为④
④通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为key和value创建一个新的Map
接下来看下ThreadLocal的set()方法
public void set(T value) {
//获取到当前线程
Thread t = Thread.currentThread();
//拿到当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//不为空,调用ThreadLocalMap 的set()方法设置值,代码在下面
map.set(this, value);
else
//为空,用ThreadLocal的引用和value作为key和value创建一个新的Map
createMap(t, value);
}
//ThreadLocalMap 的set()方法
private void set(ThreadLocal<?> key, Object value) {
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) {
//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();
}
参考
周志明:《深入理解Java虚拟机》