ThreadLocal作用以及原理解析
ThreadLocal作用
对于多个线程访问一个共享变量的时候,我们往往要通过加锁的方式进行同步,像这样
但是除此之外,其实还有另一种方式可以隔绝线程对于共享变量读写的独立性。那就是ThreadLocal。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有一块独立的空间,当多个线程操作这个变量的时候,实际上操作的都是自己线程所属的空间的那个变量,不会对其他线程有影响,也不会被其他线程影响,因为彼此都是互相独立的。因此想要保证线程安全,也可以把共享变量放在ThreadLocal中。总体来说就是,ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
接下来看一个例子
public class ThreadLocalDemo1 {
public static int value = 0;
static ThreadLocal<Object> local = new ThreadLocal<>();
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"---线程初始值:"+local.get());
local.set("我是"+Thread.currentThread().getName());
System.out.println(Thread.currentThread().getName()+"---线程修改值:"+local.get());
}
},"线程"+i).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程"+local.get());
}
}
运行结果
线程1---线程初始值:null
线程4---线程初始值:null
线程3---线程初始值:null
线程0---线程初始值:null
线程2---线程初始值:null
线程0---线程修改值:我是线程0
线程3---线程修改值:我是线程3
线程4---线程修改值:我是线程4
线程1---线程修改值:我是线程1
线程2---线程修改值:我是线程2
主线程null
上面这段代码,运行结果印证了我们开头说的那些关于ThreadLocal的论述,分析一下代码,可以看到,我们这里只有一个ThreadLocal对象,即local,我们一共有五个线程,线程0对local进行set值之后,线程2再get却还是null,但是线程0自己再get,却可以拿到自己设置的那个值 我是线程0
,别的线程是拿不到这个值的,而且代码的最后,在所有的线程都运行完毕之后,在主线程对local进行get操作,拿到的值却还是null。这就印证了这个结论,每个线程都只能拿到自己线程所属的值,线程之间是相互独立的
ThreadLocal原理
set方法
话不多说,我们直接上源码。首先先看看ThreadLocal的set方法
public void set(T value) {
//先获取当前线程
Thread t = Thread.currentThread();
//看看当前线程是不是已经设置过值了
ThreadLocalMap map = getMap(t);
//如果设置过了,就覆盖原来的值
if (map != null)
map.set(this, value);
else
//如果当前线程第一次设置值 那就创建一个存储区域(Map)
createMap(t, value);
}
继续看看 createMap(t, value)方法的源码
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
根据注释,和源码里面可以看到,createMap方法里面实际上就是new了一个ThreadLocalMap对象,创建了一个与ThreadLocal关联的map,然后把当前Thread中的一个ThreadLocal引用和要设置的值放进去。请注意t.threadLocals是定义在Thread类里的,代码如下
//这一段是定义在Thread类中的
ThreadLocal.ThreadLocalMap threadLocals = null;
继续看看ThreadLocalMap里有啥
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
可以看到,ThreadLocalMap里维护了一个Entry数组,将Thread里面的ThreadLocal.ThreadLocalMap变量引用并且通过一系列的hashcode操作,作为key。接下来继续看看Entry
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry继承WeakReference,使用弱引用,可以将ThreadLocal对象的生命周期和线程生命周期解绑,持有对ThreadLocal的弱引用,可以使得ThreadLocal在没有其他强引用的时候被回收掉,这样可以避免因为线程得不到销毁导致ThreadLocal对象无法被回收。
以上就差不多是整个set方法的源码了,有了以上的了解,再看get的就会很简单
get方法
public T get() {
//获取当前线程,并以线程引用为key去ThreadLocalMap中获取值
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果为不为空,就以当前线程对应的Thread中的ThreadLocal引用 进行hash计算 拿到值返回
if (map != null) {
//拿到引用去获取值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//为空就返回一个初始值
return setInitialValue();
}
//对应getMap方法 返回的是当前线程的threadLocals引用
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//上面的getEntry方法
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);
}
//get中的setInitialValue方法
private T setInitialValue() {
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之前没有set,也是会给你以当前线程创建一个与你线程对应的createMap的,只不过key是当前线程下的ThreadLocalMap引用,value为null,因为
T value = initialValue();
这一行的的initialValue()方法是这样的
protected T initialValue() {
return null;
}
其实还有remove方法啥的,原理都差不多.。所以就不赘述了。
总结
ThreadLocal为每个线程都定义了一个ThreadLocalMap类型名为threadLocals的变量,有点类似于HashMap,但是严格意义上来说不是,只是将其类比为Map结构,key为当前线程中的threadLocals的this引用,value是设置的值。所以每个线程都有不同的key,所能获取到的值也是不一样的,就是利用这种思想去保证值对每个线程的独立性。因为不管是get还是set之前都会有currentThread这个操作,所以每个线程都只能取到自己线程对应的值。