博客
关于我
ThreadLocal的使用总结
阅读量:370 次
发布时间:2019-03-05

本文共 5469 字,大约阅读时间需要 18 分钟。

前言

工作中用到了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");    }}

转载地址:http://ysbwz.baihongyu.com/

你可能感兴趣的文章
双指针算法思想
查看>>
分组背包问题
查看>>
子集(LeetCode 78)
查看>>
旋转数组的最小值
查看>>
1089 狼人杀-简单版
查看>>
1004 Counting Leaves (30分)
查看>>
1093 Count PAT‘s (25分) 含DP做法
查看>>
一篇解决JMM与volatile详解(二)
查看>>
数据结构之数组与经典面试题(二)
查看>>
无锁并发框架-Disruptor的使用(二)
查看>>
Android wm命令
查看>>
boot.img 解包与打包
查看>>
Android4.4 平板背光设置
查看>>
递归复习--二叉搜索树
查看>>
数据库
查看>>
jvm-02
查看>>
ThreadLocal的使用总结
查看>>
jvm-05
查看>>
spring boot@Value和bean执行顺序问题
查看>>
从浏览器输入网址到服务器返回经历的过程
查看>>