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