这几天翻阅ThreadLocal的资料,以及翻阅了jdk源码,下面整理一下思绪说说我的理解。
就像字面意思一样,ThreadLocal就是线程本地的意思,他存在的意义是为每个线程存储单独的变量,该变量在线程内可见。
网上很多资料有提到说也是为了线程完全问题,这么说其实也是对的,因为他确实可以避免线程安全问题,但是以我个人理解来看,ThreadLocal类其实已经和线程完全问题完全分离开来了,这只是分离后附属的一个好处,更重要的是变量属于线程,在线程内部可见这一信息。
我们先来看下面java代码。
public class Main {
private static StringBuilder stringBuilder = new StringBuilder();
public static void main(String[] args) {
new Thread(()->{
System.out.println("one:"+getStringBuilder());
},"one").start();
new Thread(()->{
System.out.println("two:"+getStringBuilder());
},"two").start();
}
public static StringBuilder getStringBuilder() {
stringBuilder = new StringBuilder(Thread.currentThread().getName());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return stringBuilder;
};
}
上面这一段代码别不管如何运行都是有线程完全问题,如果想要想要拿到自己各自的值的话,也如你会改成sync,让多线程同步。
这确实可以解决问题,但是现在有了ThreadLocal,sync开销大,资源浪费的问题就显示出来了。
仔细想想的话其实这里抛弃线程同步这一概念的话就可以完美的解决问题。也就是说,直接把值记录到线程内部,线程之间互不干涉内部。
举个栗子:
在不使用ThreadLocal的情况下,联合国针对一个问题作出的某项决定,只要加入了联合国的国家都是要统一遵守,但是各个国家有时候按照自己的情况,也许对于联合国的决定并不适用,这时候更想要的是根据自己的情况作出决定,这时候用了ThreadLocal就可以解决。即是说联合国或者其他国家不能干涉这个国家的内政,这个决定只在这个国家内部施行。
这时候我们把代码改成如下:
public class Main {
private static ThreadLocal<StringBuilder> local = new ThreadLocal<StringBuilder>();
public static void main(String[] args) {
new Thread(()->{
System.out.println("one:"+getStringBuilder().get());
},"one").start();
new Thread(()->{
System.out.println("two:"+getStringBuilder().get());
},"two").start();
}
public static ThreadLocal<StringBuilder> getStringBuilder() {
//把对象存到当前线程内部map中。
local.set(new StringBuilder(Thread.currentThread().getName()));
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return local;
};
}
这时候我们运行代码就会得到各自的值。
按照表象上来看,local对象只有一个,无论是线程如何存储两个线程应该都是同一local对象,那么get出来的应该也是同一对象,前半句是对的,首先需要知道的是local对象确实只有一个。
每个Thread对象内部都维护了一个ThreadLocalMap对象(该对象定义在ThreadLocal里,但是真正用的时候是在Thread类中维护),必须注意的是虽然名字中带有map,但是这个ThreadLocalMap类中并不是真正意义上的Map,ThreadLocalMap内部实际上定义了一个Entry<ThreadLocal>,Entry类是弱引用的子类,这个Entry缓存了ThreadLocal实例,我们说变量维护在各自的线程内部,实际上真正是存到了这个Entry里面。
local每次set的时候,java第一步就是会拿到当前线程的threadLocalMap,然后再根据local对象作为key获取到对应的Entry实体,最后存储你的value到entry里。
这里Entry既然继承了弱引用,那么我们的ThreadLocal什么时候不用,指向空对象了,那么gc就会回收这个垃圾对象。
下面来看源码。
local.set(new StringBuilder(Thread.currentThread().getName()));
首先获取拿到当前线程t。然后调用getMap(t),用当前线程作为参数。
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
返回了线程的threadLocals对象,这个threadLocals就是上面所说的ThreadLocalMap。
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
如果有多个threadLocal,map将会保存多个。
那么我们来看set方法源码。
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);//根据当前threadLocal对象获取对于Entry下标i。
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();//获取到的Entry后获取Entry缓存的ThreadLocal对象。
if (k == key) {//判断缓存对象k就是我们的当前threadLocal对象。
e.value = value;//是同一个threadLocal对象则保存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();
}
对象保存到了Entry的value变量中,那么我们来看看Entry类。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry有个value成员变量最后保存了我们的value值,在我们的代码中就是StringBuilder对象。Entry是缓存了当前ThreadLocal对象,所以,如果我们不用ThreadLocal了,那么,gc就会及时回收ThreadLocal垃圾对象。
下面是我画的一张图。
为了尽量能够讲清楚这件事情,我省略了细节,只需要知道,每个线程自己单独维护了map,而threadlocal是作为map的key存储进去的。而threadlocal1和threadlocal2在线程不完全的代码段上,所以不管不如的new,存进去的value值底层已经指向了不同的对象。
下面来看这段代码,如果能理解注释的三行代码的输出结果和把三行代码下面那行注释,三行注释去除的输出结果,那么基本上就立即了threadLocal的作用。
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = null;
ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();
StringBuilder stringBuilder = new StringBuilder(Thread.currentThread().getName());
new ArrayList<String>();
thread = new Thread(()->{
try{
// stringBuilder.delete(0,stringBuilder.length());
// stringBuilder.append(Thread.currentThread().getName());
// threadLocal.set(stringBuilder);
threadLocal.set(new StringBuilder(Thread.currentThread().getName()));
}catch (Exception e){
}
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("two:"+threadLocal.get());
},"two");
thread.start();
Thread.currentThread().sleep(3000);
threadLocal.set(stringBuilder);
System.out.println("main:"+threadLocal.get());//main
}
}