ThreadLocal基础
1. 什么是ThreadLocal
ThreadLocal为解决多线程并发问题提供了一种便捷的方法. 当使用ThreadLocal维护变量时ThreadLocal为每一个使用该变量的线程提供一个副本. 因此每个线程在操作ThreadLocal变量时都是在操作和自己绑定的那个副本, 上述定义会在下面的源码分析阶段得到验证.
2. 接口方法
public T get()
: 获取值.public void set(T value)
: 设置值public void remove()
: 移除值.protected T initialValue()
: 初始化值, 重写此方法可以设置初始值.
ThreadLocal基本使用
ThreadLocal 的使用非常简单, 类似于Map的使用, 只不过他会使用当前线程生产一个Key值.
/**
* ThreadLocal基本使用.
* @author WSJ
*/
public class ThreadLocalDemo {
/**
* ThreadLocal 集合.
*/
static ThreadLocal<String> mThreadLocal = new ThreadLocal<String>(){
/**
* 设置初始值.
*/
protected String initialValue() {
return "我是初始值";
};
};
public static void main(String[] args) {
System.out.println("Main-01 : " + mThreadLocal.get());
mThreadLocal.set("Main--------SetData");
new ThreadA().start();
System.out.println("Main-02 : " + mThreadLocal.get());
}
/**
* 测试线程
* @author WSJ
*/
public static class ThreadA extends Thread{
@Override
public void run() {
System.out.println("Child-02 : " + mThreadLocal.get());
mThreadLocal.set("ThreadA------SetData");
try {
sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Child-02 : " + mThreadLocal.get());
}
}
}
输出结果如下 :
Main-01 : 我是初始值
Main-02 : Main--------SetData
Child-02 : 我是初始值
Child-02 : ThreadA------SetData
从输出结果上看, 确实证实了, ThreadLocal 在每一个线程中都会有自己的一个副本. 因此修改的只是和当前变量相关的变量副本.不会影响其他的线程中的副本.
ThreadLocal源码分析
其实ThreadLocal的实现原理也十分简单. 结合源码来分析一下. 介绍ThreadLoacal源码前我们需要先来看一个类ThreadLocalMap
类. ThreadLocalMap
其实就是一个自定义的Map类.
ThreadLocalMap 内部使用动态数组, 来保存数据. 用法和Map一样.部分源码如下
/**
* 数据实体类, 继承自WeakReference, 弱引用.
* Key : WeakReference 的值本身,
* value : 是要存储的数据.
*/
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;
// 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) {
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();
}
// get 方法.
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);
}
通过上面的部分源码可以看出, ThreadLocalMap的实现和HashMap的实现类似. 可以暂时将他看成Map.只不过他的Key只能是ThreadLocal对象.
来看看ThreadLocal源码吧.
/**
* ThreadLocal 源码分析
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal<T> {
/**
* 自定义Hash值,用来作为Key
*/
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* 初始化值, 如果再一个线程中没有设置ThreadLocal的值,
* 直接get()则会获取到这个方法的返回值.
*/
protected T initialValue() {
return null;
}
/**
* 无参构造方法.
*/
public ThreadLocal() {
}
/**
* 获取值的方法.
*/
public T get() {
// 1. 获取当前线程对象.
Thread t = Thread.currentThread();
// 2. 根据当前线程对象获取到ThreadLocalMap对象.
ThreadLocalMap map = getMap(t);
if (map != null) {
// 3. 获取以当前ThreadLocal为Key的实体对象.
ThreadLocalMap.Entry e = map.getEntry(this);
// Entry的Value就是实际值.
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果没有设置值, 则返回初始化值.
return setInitialValue();
}
/**
* 设置初始化值,
*/
private T setInitialValue() {
// 获取初始化值,可以重写 initialValue() 方法来设置初始值.
T value = initialValue();
// 保存值.
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// 判断当前的线程对象是否绑定了一个ThreadLocalMap对象,
if (map != null)
// 有直接保存数据, ThreadLocal作为Key.
map.set(this, value);
else
// 没有ThreadLocalMap对象,则创建对象.
createMap(t, value);
return value;
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. 设置当前线程中的ThreadLocal的副本的值.
*/
public void set(T value) {
// 获取当前Thread对象.
Thread t = Thread.currentThread();
// 获取和当前ThreadLocal绑定的ThreadLocalMap对象.
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);// 直接保存
else
createMap(t, value); // 创建新的ThreadLocalMap对象.
}
/**
* Removes the current thread's value for this thread-local
* variable. 移除当前线程中ThreadLocal变量的副本值.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* 获取当前线程的ThreadLocal, ThreadLocalMap 就是一个自定一的Map类.
*/
ThreadLocalMap getMap(Thread t) {
// Thread类中保存着.ThreadLocalMap对象.
return t.threadLocals;
}
/**
* 创建一个和当前ThreadLocal绑定的
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
代码中已经有了详细的注释, 接下来只是一些说明.
ThreadLocal为每一个线程提供副本的实现方法
Thread类源码片段
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal#createMap() 方法源码
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
通过上面提供的两个源码片段可以看出每一个线程都保存着一个ThreadLocalMap对象, 并且使用当前ThreadLocal对象作为Key值.也就是说实现的方式相当于创建多个Map集合, 使用同一个Key来保存数据,这样的话, 虽然Key值相同但是在不同的集合中可以存储不同的值.