这是Lucene 源代码中的一句注释你可以参考下 全部的代码如下 package org.apache.lucene.util;/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */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<T> implements Closeable { private ThreadLocal<WeakReference<T>> t = new ThreadLocal<>(); // Use a WeakHashMap so that if a Thread exits and is GC'able, its entry may be removed: private Map<Thread, T> 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<T> 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 (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<Thread> 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; } }
/** * 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 */
1. 容易造成内存泄漏。thread local 是实际是两级以上的hashtable,一旦你还用线程池的话,用的不好可能永远把内存占着。造成java VM上的内存泄漏。
2. 代码的耦合度高,且测试不易。
第1个不是问题,使用 ThreadLocal 主要是提供一个类似 Context 的东西,在进入边界时初始化,退出边界时清空现场,比如 Spring Hibernate 在开启一个新的 Servlet 请求时在监听器中先初始化 session,结束后清理现场。我们不要把这个 ThreadLocal 滥用嘛。像反编译 WebSphere 5.1 事务管理部分的代码,它也是用 ThreadLocal 跟踪 EJB 当前事务绑定的,这种绑定正好适合实现 EJB 对事务绑定的支持,简单,直观地管理事务绑定。有了这个 Context,其它地方不需要明确地互相传递 transaction 了,自己去拿就是了。参考前面。
第1个不是问题,使用 ThreadLocal 主要是提供一个类似 Context 的东西,在进入边界时初始化,退出边界时清空现场,比如 Spring Hibernate 在开启一个新的 Servlet 请求时在监听器中先初始化 session,结束后清理现场。我们不要把这个 ThreadLocal 滥用嘛。像反编译 WebSphere 5.1 事务管理部分的代码,它也是用 ThreadLocal 跟踪 EJB 当前事务绑定的,这种绑定正好适合实现 EJB 对事务绑定的支持,简单,直观地管理事务绑定。有了这个 Context,其它地方不需要明确地互相传递 transaction 了,自己去拿就是了。参考前面。
第一个绝对是问题,不用线程池无所谓,但用线程池的话,必须小心。实在不行,只能在线程程外面来清理。
在线程池中也是一样的,比如,一个 servlet request (我们都知道它背后肯定是一个线程池的 worker 线程):下面这段代码有语法错误,主要是演示用法,不管是不是在线程池中我们都可以有一个 Context,同时也要对它的生命周期规律了解彻底(比如 servlet request 是个什么生命周期,它结合 servlet filter 的工作过程是什么样的),找到我们的 Context 要起作用的 boundary 边界在哪里,这个边界没控制精确就导致各种错误或内存消耗过大或事务关联搞错了等:
... static final ThreadLocal sessions = new ThreadLocal();
... doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
Session context = new Session();
sessions.set(context); try {
chain.doFilter(request, response);
} finally {
session.close();
sesssions.set(null);
}
}第1个不是问题,使用 ThreadLocal 主要是提供一个类似 Context 的东西,在进入边界时初始化,退出边界时清空现场,比如 Spring Hibernate 在开启一个新的 Servlet 请求时在监听器中先初始化 session,结束后清理现场。我们不要把这个 ThreadLocal 滥用嘛。像反编译 WebSphere 5.1 事务管理部分的代码,它也是用 ThreadLocal 跟踪 EJB 当前事务绑定的,这种绑定正好适合实现 EJB 对事务绑定的支持,简单,直观地管理事务绑定。有了这个 Context,其它地方不需要明确地互相传递 transaction 了,自己去拿就是了。参考前面。
第一个绝对是问题,不用线程池无所谓,但用线程池的话,必须小心。实在不行,只能在线程程外面来清理。
public class Context {
static final ThreadLocal contexts = new ThreadLocal();
public static Context getCurrentContext() { return contexts.get(); }
protected static void init() { contexts.set(new Context()); }
protected static void destroy() { contexts.set(null);}
private Context() { };
// 其它框架级别的业务方法。
}
... doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
Context.init(); try {
chain.doFilter(request, response);
// 在这个 context 边界界定的范围内我们的其它的 API 都可以 Context.getCurrentContext() 来得到它的 Context 对象,而不会在线程之间出现共用和线程安全问题。
} finally {
Context.destroy();
}
}
全部的代码如下
package org.apache.lucene.util;/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/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<T> implements Closeable { private ThreadLocal<WeakReference<T>> t = new ThreadLocal<>(); // Use a WeakHashMap so that if a Thread exits and is GC'able, its entry may be removed:
private Map<Thread, T> 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<T> 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 (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<Thread> 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;
}
}
* 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
*/