博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
ThreadLocal缺陷以及处理
阅读量:4060 次
发布时间:2019-05-25

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

分析过ThreadLocal源码源码的人都知道,ThreadLocal的设计的确巧妙,但是它也有一个缺陷:可能会引起内存泄漏;ThreadLocalMap中key维护着一个weakReference,它在下次GC之前会被清理,如果Value仍然保持着外部的强引用,该ThreadLocal没有再进行set,get或者remove操作,时间长了就可能导致OutOfMemoryError .

lucene中的类CloseableThreadLocal对ThreadLocal做了处理,优化了其缺陷.学习一下:

当执行CloseableThreadLocal.set(T)时,内部其实只是把值赋给内部的ThreadLocal对象,即执行ThreadLocal.set(new WeakReference(T)),将T包装成弱引用对象,目的就是当内存不足时,jvm可以回收此对象.

浙江引入一个新的问题:当前线程还存活着的时候,因为内存不足而回收了弱引用对象,这样会在下次调用get()时取不到值返回null,这是不可接受的.所以CloseableThreadLocal在内部还创建了一个私有的Map,WeakHashMap<Thread, T>,当线程只要存活时,则T就至少有一个引用存在,所以不会被提前回收。同时要注意另一个问题,要对WeakHashMap的操作使用synchronized做同步.

package org.apache.lucene.util;import java.io.Closeable;import java.lang.ref.WeakReference;import java.util.Iterator;import java.util.Map;import java.util.WeakHashMap;import java.util.concurrent.atomic.AtomicInteger;/** Java's builtin ThreadLocal has a serious flaw: *  it can take an arbitrarily long amount of time to *  dereference the things you had stored in it, even once the *  ThreadLocal instance itself is no longer referenced. *  This is because there is single, master map stored for *  each thread, which all ThreadLocals share, and that *  master map only periodically purges "stale" entries. * *  While not technically a memory leak, because eventually *  the memory will be reclaimed, it can take a long time *  and you can easily hit OutOfMemoryError because from the *  GC's standpoint the stale entries are not reclaimable. *  *  This class works around that, by only enrolling *  WeakReference values into the ThreadLocal, and *  separately holding a hard reference to each stored *  value.  When you call {@link #close}, these hard *  references are cleared and then GC is freely able to *  reclaim space by objects stored in it. * *  We can not rely on {@link ThreadLocal#remove()} as it *  only removes the value for the caller thread, whereas *  {@link #close} takes care of all *  threads.  You should not call {@link #close} until all *  threads are done using the instance. * * @lucene.internal */public class CloseableThreadLocal
implements Closeable {
//将值T用弱引用包裹, private ThreadLocal
> t = new ThreadLocal<>(); // Use a WeakHashMap so that if a Thread exits and is // GC'able, its entry may be removed: //使用WeakHashMap,这样如果一个Thread退出并且是可GC,它Entry可能将被删除: //亦即WeakHashMap
,当线程只要存活时,则T就至少有一个引用存在,所以不会被提前回收 private Map
hardRefs = new WeakHashMap<>(); // Increase this to decrease frequency of purging in get: private static int PURGE_MULTIPLIER = 20; // On each get or set we decrement this; when it hits 0 we // purge. After purge, we set this to // PURGE_MULTIPLIER * stillAliveCount. This keeps // amortized cost of purging linear. private final AtomicInteger countUntilPurge = new AtomicInteger(PURGE_MULTIPLIER); protected T initialValue() {
return null; } public T get() {
WeakReference
weakRef = t.get(); if (weakRef == null) { T iv = initialValue(); if (iv != null) { set(iv); return iv; } else { return null; } } else { maybePurge(); return weakRef.get(); } } public void set(T object) { t.set(new WeakReference<>(object)); //使用synchronized同步 synchronized(hardRefs) { hardRefs.put(Thread.currentThread(), object); maybePurge(); } } private void maybePurge() { if (countUntilPurge.getAndDecrement() == 0) { purge(); } } // Purge dead threads private void purge() { synchronized(hardRefs) { int stillAliveCount = 0; for (Iterator
it = hardRefs.keySet().iterator(); it.hasNext();) { final Thread t = it.next(); if (!t.isAlive()) { it.remove(); } else { stillAliveCount++; } } int nextCount = (1+stillAliveCount) * PURGE_MULTIPLIER; if (nextCount <= 0) { // defensive: int overflow! nextCount = 1000000; } countUntilPurge.set(nextCount); } } @Override public void close() { // Clear the hard refs; then, the only remaining refs to // all values we were storing are weak (unless somewhere // else is still using them) and so GC may reclaim them: hardRefs = null; // Take care of the current thread right now; others will be // taken care of via the WeakReferences. if (t != null) { t.remove(); } t = null; }}

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

你可能感兴趣的文章
Jenkins + Docker + SpringCloud 微服务持续集成 - 高可用集群部署(三)
查看>>
Golang struct 指针引用用法(声明入门篇)
查看>>
Linux 粘滞位 suid sgid
查看>>
C#控件集DotNetBar安装及破解
查看>>
Winform皮肤控件IrisSkin4.dll使用
查看>>
Winform多线程
查看>>
C# 托管与非托管
查看>>
Node.js中的事件驱动编程详解
查看>>
mongodb 命令
查看>>
MongoDB基本使用
查看>>
mongodb管理与安全认证
查看>>
nodejs内存控制
查看>>
nodejs Stream使用中的陷阱
查看>>
MongoDB 数据文件备份与恢复
查看>>
数据库索引介绍及使用
查看>>
MongoDB数据库插入、更新和删除操作详解
查看>>
MongoDB文档(Document)全局唯一ID的设计思路
查看>>
mongoDB简介
查看>>
Redis持久化存储(AOF与RDB两种模式)
查看>>
memcached工作原理与优化建议
查看>>