有没有办法列出绑定到线程的TheadLocals?理想情况下,我可以访问Thread.threadLocals映射,但它受包保护。
我需要这个的原因是我需要检查线程,因为它们被返回到线程池以确保已正确清理ThreadLocals。也许有另一种方法可以做到这一点?
答案 0 :(得分:11)
Listing ThreadLocals和Clearing ThreadLocals可以通过使用反射(和the setAccessible() flag)来覆盖JVM的常用权限来完成。出于显而易见的原因,当所有安全机制到位时,这都不可用。
答案 1 :(得分:2)
只要知道要清理哪些变量,就可以使用线程池的afterExecute
方法进行清理(重新初始化?)。
否则你可以使用反射 - 从线程内部迭代你感兴趣的类的声明字段,对于每个类型是ThreadLocal实例的每个字段,set
它到它的{ {1}}关于你关心的对象。
答案 2 :(得分:2)
沿着'另一个好方法',我创建了一个Runnable包装器,它获取了预先存在的线程本地的快照,运行嵌套的runnable,然后清除(设置为null)本地的任何线程最初没有出现。
这可以通过将'snapshot'代码放入子类Executor的beforeExecute()和@danben建议的afterExecute中的'cleanup'代码来做得更好。
无论哪种方式,美丽的是你不必硬编码保留或丢弃哪个线程本地。
从源列表中删除了异常处理以避免混乱。
public class ThreadLocalCleaningRunnable implements Runnable
{
private final Runnable runnable;
public ThreadLocalCleaningRunnable(Runnable runnable) {
this.runnable = nonNull(runnable);
}
public void run() {
// printThreadLocals();
Set> initialThreadLocalKeys = getThreadLocalKeys();
try {
runnable.run();
}
finally {
cleanThreadLocalsExcept(initialThreadLocalKeys);
// printThreadLocals();
}
}
public static void printThreadLocals() {
Thread thread = Thread.currentThread();
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
Class threadLocalMapKlazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
Field tableField = threadLocalMapKlazz.getDeclaredField("table");
tableField.setAccessible(true);
Object threadLocals = threadLocalsField.get(thread);
if (threadLocals != null) {
Object table = tableField.get(threadLocals);
if (table != null) {
int threadLocalCount = Array.getLength(table);
String threadName = thread.getName();
for (int i = 0; i > getThreadLocalKeys() {
Thread thread = Thread.currentThread();
Set> threadLocalKeys = new HashSet>();
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
Class threadLocalMapKlazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
Field tableField = threadLocalMapKlazz.getDeclaredField("table");
tableField.setAccessible(true);
Object threadLocals = threadLocalsField.get(thread);
if (threadLocals != null) {
Object table = tableField.get(threadLocals);
if (table != null) {
int threadLocalCount = Array.getLength(table);
for (int i = 0; i ) entry).get();
if (o instanceof ThreadLocal) {
threadLocalKeys.add((ThreadLocal) o);
}
}
}
}
}
return threadLocalKeys;
}
public static void cleanThreadLocalsExcept(Set> keptThreadLocalKeys) {
Thread thread = Thread.currentThread();
Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
threadLocalsField.setAccessible(true);
Class threadLocalMapKlazz = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
Field tableField = threadLocalMapKlazz.getDeclaredField("table");
tableField.setAccessible(true);
Object threadLocals = threadLocalsField.get(thread);
if (threadLocals != null) {
Object table = tableField.get(threadLocals);
if (table != null) {
int threadLocalCount = Array.getLength(table);
for (int i = 0; i ) entry).get();
if (o instanceof ThreadLocal) {
ThreadLocal tl = (ThreadLocal) o;
if (!keptThreadLocalKeys.contains(tl)) {
Field valueField = entry.getClass().getDeclaredField("value");
valueField.setAccessible(true);
valueField.set(entry, null);
}
}
}
}
}
}
}
}
答案 3 :(得分:1)
我需要这个的原因是我需要检查线程,因为它们被返回到线程池以确保已正确清理ThreadLocals。
就我个人而言,我认为在线程池线程上使用线程本地数据是一种不好的做法。如果你真的需要线程本地状态,你应该自行管理线程,这样你就可以明确地清理数据。
线程池线程具有不确定的生命周期,因此您不应该依赖显式托管的本地线程数据。
答案 4 :(得分:0)
您可以通过创建一个Runnable实现来使用类似AOP的构造,该实现通过您自己的实现包装原始Runnable。它将调用原始Runnable的run方法,然后在线程的上下文中执行您需要的任何其他清理,这将允许您调用ThreadLocal.remove()方法。然后,将此包装器提供给线程池。这适用于任何线程池实现(例如没有before / afterExecute方法的那些)
答案 5 :(得分:0)
从消息来源来看,它看起来非常紧张。一切都是Thread或ThreadLocal的私有。
您可能能够通过一个检测代理执行您需要的操作,方法是重新定义ThreadLocal以添加一个将当地线程转储本地的方法。
以下是我发现的向现有类添加日志记录的示例:http://today.java.net/pub/a/today/2008/04/24/add-logging-at-class-load-time-with-instrumentation.html
您需要使用BCEL或JavaAssist修补ThreadLocal字节码以添加方法。稍后,您需要使用反射来获取方法的句柄,以便您可以调用它。
注意:如果您在受限制的环境(applet或appserver)中运行,这可能不起作用,因为安全机制通常会阻止您查看系统类。