ThreadLocal的使用总结

前言

工作中用到了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");
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal是一个Java类,用于在多线程环境下保存线程本地变量的副本。通过创建ThreadLocal对象,每个线程都可以拥有自己独立的变量副本,互不干扰。 使用ThreadLocal的过程如下: 1. 创建一个ThreadLocal对象,可以指定泛型类型。 2. 在每个线程中,通过ThreadLocal对象的`get()`方法获取当前线程的变量副本。如果当前线程没有设置过变量值,则会使用默认值进行初始化。 3. 在每个线程中,通过ThreadLocal对象的`set(value)`方法设置当前线程的变量值。 4. 在每个线程中,通过ThreadLocal对象的`remove()`方法移除当前线程的变量副本。 需要注意的是,ThreadLocal对象的生命周期与Thread对象的生命周期一样长。当ThreadLocal对象被垃圾回收时,关联的变量副本也会被回收。 在ThreadLocal内部,使用ThreadLocalMap来存储每个线程的变量副本。ThreadLocal的实例作为key,变量值作为value。ThreadLocalMap可以使用强引用或弱引用来引用ThreadLocal对象。如果使用强引用,当ThreadLocal对象被回收时,如果没有手动删除对应的变量副本,会导致内存泄漏。如果使用弱引用,当ThreadLocal对象被回收时,对应的变量副本也会被回收。 总结来说,ThreadLocal是一种线程本地变量,通过保存每个线程的变量副本,实现了多线程环境下的线程隔离。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ThreadLocal使用与原理](https://blog.csdn.net/qq_35190492/article/details/116431270)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [ThreadLocal使用详解](https://blog.csdn.net/LJJZJ/article/details/88763666)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值