正确理解ThreadLocal:ThreadLocal中的值并不一定是完全隔离的
首先再讨论题主的这个观点之前我们要明确一下ThreadLocal的用途是什么?
ThreadLocal并不是用来解决共享对象的多线程访问问题。
看了许多有关ThreadLocal的博客,看完之后会给人一种错觉,ThreadLocal就是用于在多线程情况下防止共享对象的线程安全问题,使用ThreadLocal之后,ThreadLocal的对象就不会有线程安全问题,但是一定是这样么,看如下代码
-
public class test {
-
public static void main(String[] args) throws InterruptedException {
-
new A().start();
-
new A().start();
-
new A().start();
-
new A().start();
-
}
-
-
static class A extends Thread {
-
static List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
-
static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
-
-
protected List<Integer> initialValue() {
-
return list;
-
}
-
};
-
-
-
public void run() {
-
List<Integer> threadList = threadLocal.get();
-
threadList.add(threadList.size());
-
System.out.println(threadList.toString());
-
}
-
-
}
-
}
该代码很简单,就是在多线程的情况下输出ThreadLocal的list集合状态,如果此时线程安全的话,输出的4个语句应该是完全一样的输出结果如下:
[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5, 6]
[1, 2, 3, 4, 5, 5, 6, 7]
[1, 2, 3, 4, 5, 5, 6, 7, 8]
很明显可以看到,线程是不安全的,从结果也能看出来4个线程中ThreadLocal中的List是同一个对象,被四个线程所共享。
接下来我们分析一下原因的发生原因,我们去看ThreadLocal的get()方法
-
public T get() {
-
Thread t = Thread.currentThread();
-
ThreadLocalMap map = getMap(t);
-
if (map != null) {
-
ThreadLocalMap.Entry e = map.getEntry(this);
-
if (e != null) {
-
-
T result = (T)e.value;
-
return result;
-
}
-
}
-
return setInitialValue();
-
}
如果一个线程第一次调用threadLocal.get()方法时,我们通过调试可以发现此时拿到的map是null,会调用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;
-
}
可以看到这个方法里面有一个value,而这个value就是ThreadLocal中即将要保存的只对线程所见的"副本",而这个value使用过initialValue()方法得到的,这个方法如果你没有重写的话默认返回时一个null,而在我们的例子当中他返回的是我们定义的静态变量list,而这个list是一个单例的(只会被类第一次访问时进行初始化),也就是说我们的例子中每个线程的ThreadLocal里面get的都是同一个list,所以就造成了线程安全的问题.
接下来改善一下我们的代码
-
static class A extends Thread {
-
static ThreadLocal<List<Integer>> threadLocal = new ThreadLocal<List<Integer>>() {
-
-
protected List<Integer> initialValue() {
-
return new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
-
}
-
};
-
-
-
public void run() {
-
List<Integer> threadList = threadLocal.get();
-
threadList.add(threadList.size());
-
System.out.println(threadList.toString());
-
}
-
-
}
运行结果
[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5]
[1, 2, 3, 4, 5, 5]
接下来回到主题,ThreadLocal的作用是什么:
ThreadLocal使得各线程能够保持各自独立的一个对象,而实现原理其实是通过,每个线程都会重新创建一个对象,不是什么对象的拷贝或副本,而线程是否安全取决于你如何去创建这个对象。
然后总结一下:
1.ThreadLocal作用不是为了解决共享对象的多线程安全问题,而是为了避免通多参数传递的方式去拿到一个对象,网上有些例子就一开始拿线程安全举例子,然后抛砖引玉出ThreadLocal,会把人带偏。。。博主就是一个。
2.ThreadLocal中保存的不是什么对象的副本,里面没有需要保存的对象做任何复制,拷贝操作,这个对象完全取决于你的iniialtValue方法中如何去创建,所以这里需要考虑使用ThreadLocal的性能问题,是否会大量创建一个对象。