一文掌握ThreadLocal

一,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()方法主动清理好一些。
关于强弱引用,可以见我这样文章:
强引用软引用弱引用虚引用使用场景与注意事项

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值