一,ThreadLocal是什么?
threadlocal是一个创建线程局部变量的类。
通过该方法可以使一个对象变成该线程独享,从而实现线程安全。
比如SimpleDateFormat 类是非线程安全的,我们使用ThreadLocal将其变成线程安全的对象。
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
ThreadLocal<SimpleDateFormat> threadLocal=new ThreadLocal();
threadLocal.set(simpleDateFormat);
threadLocal.get();
上述代码可以起到线程隔离的作用吗?
不能!!!
这样使用跟存放在各个线程的map中没什么区别,由于对象是同一个,自然无法实现线程隔离。
正确的打开方式是这样,也只有以下这种方式,可以实现线程隔离。
ThreadLocal<SimpleDateFormat> threadLocal2=new ThreadLocal(){
@Override
protected Object initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
SimpleDateFormat simpleDateFormat3 = threadLocal.get();
threadLocal.remove();
二,ThreadLocal为什么有这样的作用?
我们来看源码实现:
1.首先看Threadlocal类的set方法
public void set(T value) {
Thread t = Thread.currentThread(); //1
ThreadLocalMap map = getMap(t); //2
if (map != null)
map.set(this, value); //3
else
createMap(t, value); //4
}
1.获取当前线程
2.获取该线程里的ThreadLocalMap对象
3.如果ThreadLocalMap对象不为null,则设置,key就是当前对象本身,value是传入的值。
4.如果map为null,生成map 并赋值。
getmap方法,获取的是当前线程下的threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
我们看下Thread类中的相关代码,存在ThreadLocalMap变量:
ThreadLocal.ThreadLocalMap threadLocals = null;
createmap方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
将本线程中的threadLocals 指向新建的ThreadLocalMap。
综上,每个Thread中都有一个ThreadLocalMap,该map 中存有已当前对象为key ,输入参数为value的k-v对。
2.现在看下get方法的实现
public T get() {
Thread t = Thread.currentThread(); //1
ThreadLocalMap map = getMap(t); //2
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //3
if (e != null) {
@SuppressWarnings("unchecked") //4
T result = (T)e.value; //5
return result;
}
}
return setInitialValue(); //6
}
1.获取当前线程
2。获取当前线程下的ThreadLocalMap对象
3.如果该对象不为null,在该map只能够取出以当前对象为key的value。
4.抑制编译器强转警告
5.转化类型
6.如果map 为null,生成初始值
总的来说就是获取当前线程下threadlocalmap中,以当前对象为key的value;
再看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;
}
initialValue()
protected T initialValue() {
return null;
}
上面两个方法就是生成一个key 为当前对象,值为null的ThreadLocalMap对象。
这个方法就是实现线程隔离的关键所在,我们使用时要重写该方法,以便给每个线程新建一个对象。这样就实现了线程隔离。
从以上源码可以看出,这个ThreadLocalMap对象是属于某个线程的,自然也就实现了线程的独享,从而实现了线程安全。
三,ThreadLocal真的只能被一个线程访问么?
不是的,我们看Thread源码中的另一个变量:
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
什么时候使用该变量呢?
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,long stackSize){
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); //1
....
}
1.在Thread对象生成的时候,执行init方法,其中存在
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
将父线程的inheritableThreadLocals map继承给了自己。
所以,我们可以在父类中定义InheritableThreadLocals对象,该对象就会层层继承下去,从而实现父向子线程的变量共享。
见测试代码:
public static void main(String[] args) {
ThreadLocal threadLocal=new ThreadLocal();
threadLocal.set("test");
new Thread(){
public void run(){
super.run();
System.out.println(threadLocal.get());
}
}.start();
}
打印:
null
说明子线程没有获取到父线程的ThreadLocal内的对象
将new ThreadLocal()改为new InheritableThreadLocal()
public static void main(String[] args) {
ThreadLocal threadLocal=new InheritableThreadLocal();
threadLocal.set("test");
new Thread(){
public void run(){
super.run();
System.out.println(threadLocal.get());
}
}.start();
}
打印:
test
说明 InheritableThreadLocal被传递进了子线程。
那么问题来了,Thread类源码中并没有InheritableThreadLocal的设置方法,子线程是怎么获取到的该对象呢?
看InheritableThreadLocal类源码:
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
该类很简单,继承ThreadLocal并重写类getMap,createMap两个方法。所以当我们执行
ThreadLocal threadLocal=new InheritableThreadLocal();
threadLocal.set("test");
在InheritableThreadLocal类中,将t.inheritableThreadLocals,设置为了以当前对象为key,以value为值的的map。
从而可以传递给子线程。
四,会导致内存泄露么?
首先,加入不使用线程池,那么当线程结束时,该线程中定义的变量就会被释放,不会引起内存泄漏。
当使用线程池时,由于线程可能不会结束,而是用来复用。那么当我们只定义threadlocal变量不回收时,确实可能造成内存泄漏。所以,threadlocal变量一定要通过
threadLocal.remove();
进行回收。
该方法中,对该key对应键值的引用值为null。没有引用了,gcroot不可达,自然会被gc回收掉。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
public void clear() {
this.referent = null;
}
五,主动回收还会导致内存泄露么?
不会的。
有这种担心的,应该是想到了如果我们写threadlocal=null,我们是将threadlocal对象当作key来传给thread类的,所以这时候thread类中还存在ThreadLocalMap->entry->key的引用,所以该对对象并不能被释放,造成内存泄漏。不过entry的持有threadlocal是弱引用,所以entry key不会内存泄漏。
不过存在entry value这时未能释放。好在threadlocalmap,在set操作时会主动清空key为null的entry,所以也不存在内存泄漏的问题。
不过还是通过threadlocal.remove()方法主动清理好一些。
关于强弱引用,可以见我这样文章:
强引用软引用弱引用虚引用使用场景与注意事项