ThreadLocal的使用总结

  • Post author:
  • Post category:其他




前言

工作中用到了ThreadLocal,觉得非常巧妙好用,顾总结下



ThreadLocal 通常使用场景
  • 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束
  • 线程间数据隔离
  • 进行事务操作,用于存储线程事务信息。
  • 数据库连接,Session会话管理。

工作中就是利用了ThreadLocal省去了频繁的传递参数。



使用

先写一个工具类

public class VersionHodler {

    private static ThreadLocal<docVersion> docVersionMap = new ThreadLocal<>();


    public static void  setVersionMap(docVersion versionMap){
        docVersionMap.set(versionMap);
    }

    public static docVersion getVersionMap(){
        return docVersionMap.get();
    }


	//用完清除避免内存泄漏
    public static void  clear(){
        topicDocVersionMap.remove();
    }

	//传递一个内部类作为介质
    public static class  DocVersion{

        private Integer finishStatus;

        private Integer topicVersion;

        public Integer getFinishStatus() {
            return finishStatus;
        }

        public void setFinishStatus(Integer finishStatus) {
            this.finishStatus = finishStatus;
        }

        public Integer getTopicVersion() {
            return topicVersion;
        }

        public void setTopicVersion(Integer topicVersion) {
            this.topicVersion = topicVersion;
        }
    }
}

使用时 ,先在需要传递参数的业务代码那里设值:

//这里省略业务代码,如某个controller调用了n多逻辑,controller-->service1-->service2-->dao-->某save方法

try {
                            VersionHodler.DocVersion docVersion = new VersionHodler.docVersion();
                            docVersion.setFinishStatus(xxx.getFinishStatus());
                            docVersion.setTopicVersion(xxx.getTopicVersion());
                            VersionHodler.setVersionMap(docVersion);
                    }
                    finally {
                        VersionHodler.clear();
                    }

最后需要在使用这个变量的地方,如某save方法中:

//先把变量取出来
VersionHodler.DocVersion docVersion  = VersionHodler.getVersionMap();
//执行代码

最后,好处应该可以看到了

controller-->service1-->service2-->dao-->某save方法

当中不需要传递参数,直接在需要取参数的地方就能取到,避免频繁的传递参数,也没有使用到锁机制。如果某场景下代码量已经很多 ,后期由于某个需求需要添加一个参数,那么这个改动量是非常大的。



内存泄漏问题

使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

为什么会内存溢出呢?看源码(jdk1.8)~

1.首先看ThreadLocal里面保存的是什么值。看set方法。

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

首先获取到了当前线程t,然后调用getMap获取ThreadLocalMap,如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去。如果该Map不存在,则初始化一个。

2.再看ThreadLocalMap是什么

static class ThreadLocalMap {
        /**
         * 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;
            }
        }
        //... 省略
}

ThreadLocalMap其实就是ThreadLocal的一个静态内部类,里面定义了一个Entry来保存数据,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。

还有一个getMap

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

调用当期线程t,返回当前线程t中的成员变量threadLocals。而threadLocals其实就是ThreadLocalMap。



get方法
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

首先获取当前线程,然后调用getMap方法获取一个ThreadLocalMap,如果map不为null,那就使用当前线程作为ThreadLocalMap的Entry的键,然后值就作为相应的的值,如果没有那就设置一个初始值。



内存泄漏的原因
  • Thread中有一个map,就是ThreadLocalMap
  • ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。
  • ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收
  • 如果突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样(注意这几个变量生命周期不一样,品一下),它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

    解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。


额外加深理解Java内的四大引用
  • 强引用:强引用是使用最普通的应用。如果一个对象具有强引用,那么gc绝

    不会回收

    它。当内存空间不足,java虚拟机宁愿抛出OOM(OutOfMemory),使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。
  • 软引用:如果一个对象具有软引用,在JVM发生内存溢出之前(即内存充足够使用),是不会GC这个对象的;

    只有到JVM内存不足的时候才会调用垃圾回收期回收掉这个对象

    。软引用和一个引用队列联合使用,如果软引用所引用的对象被回收之后,该引用就会加入到与之关联的引用队列中。(

    软引用可以用来实现内存敏感的高速缓存

  • 弱引用:这里讨论ThreadLocalMap中的Entry类的重点,如果一个对象只具有弱引用,那么这个对象就会被垃圾回收器回收掉(被弱引用所引用的对象只能生存到下一次GC之前,

    当发生GC时候,无论当前内存是否足够,弱引用所引用的对象都会被回收掉

    )。弱引用也是和一个引用队列联合使用,如果弱引用的对象被垃圾回收期回收掉,JVM会将这个引用加入到与之关联的引用队列中。若引用的对象可以通过弱引用的get方法得到,当引用的对象被回收掉之后,再调用get方法就会返回null。
  • 虚引用:虚引用是所有引用中最弱的一种引用,其存在就是为了将关联虚引用的对象在被GC掉之后收到一个通知。


软引用作为缓存demo
public class SoftReferenceDemo {
    //private static ConcurrentHashMap<String,SoftReference<User>> cacheUser = new ConcurrentHashMap<String,SoftReference<User>>();
    public static void main(String[] args) throws InterruptedException {
        User a = new User();
        a.name = "wei";
        SoftReference<User> softReference = new SoftReference<User>(a);
        a = null;
        System.out.println(softReference.get().name);
        int i = 0;

        while (softReference.get() != null) {
            if (i == 10) {
                System.gc();
                System.out.println("GC");
            }
            Thread.sleep(1000);
            System.out.println(i);
            i++;
        }
        System.out.println("Finish");
    }
}



版权声明:本文为zquwei原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。