开发新Blocking Queue时,如何检测对象的创建线程?
我正在开发一个新的阻塞队列,需要追踪队列中对象的创建线程来源。目前我实现了一个ThreadUniqueObject基类,要求队列中的对象必须继承它:
/** * 一个不可变、线程安全的对象,由创建它的线程唯一标识。所有子类必须在构造方法中调用父类构造, * 且在判断相等性时需同时确保父类的相等性。 * * 若子类选择扩展该对象并使其表示可变,请注意该对象仍会被视为由构造它的线程创建。 */ public class ThreadUniqueObject { private final long threadId; public long getThreadId() { return threadId; } public ThreadUniqueObject() { this.threadId = Thread.currentThread().getId(); } @Override public boolean equals(Object obj) { if(!(obj instanceof ThreadUniqueObject)) return false; return ((ThreadUniqueObject)obj).getThreadId() == this.getThreadId(); } @Override public int hashCode() { return (int)threadId; } }
但我希望找到不需要让对象继承该类的方案——毕竟反射也找不到JVM中标记对象创建线程的私有字段。请问有哪些可行的思路?
可行的替代方案
1. 用包装类无侵入包裹对象
你可以创建一个通用的包装容器,不需要修改目标对象的继承结构,而是在对象入队时用这个容器包裹它,同时记录创建线程ID:
public class ThreadTrackedWrapper<T> { private final T target; private final long creatorThreadId; private ThreadTrackedWrapper(T target) { this.target = target; this.creatorThreadId = Thread.currentThread().getId(); } public static <T> ThreadTrackedWrapper<T> wrap(T target) { return new ThreadTrackedWrapper<>(target); } public T getTarget() { return target; } public long getCreatorThreadId() { return creatorThreadId; } }
当对象入队时,调用ThreadTrackedWrapper.wrap(yourObject),队列实际存储包装后的实例即可。这种方式完全不侵入原有对象的代码,还能灵活适配任意类型的对象。
2. 用工厂模式+WeakHashMap关联线程与对象
如果能控制对象的创建入口,可以用工厂类统一创建对象,并在创建时记录线程与对象的关联:
import java.util.Map; import java.util.WeakHashMap; public class ObjectCreationTracker { private static final Map<Object, Long> objectToThreadId = new WeakHashMap<>(); public static <T> T createAndTrack(Supplier<T> creator) { T obj = creator.get(); objectToThreadId.put(obj, Thread.currentThread().getId()); return obj; } public static Long getCreatorThreadId(Object obj) { return objectToThreadId.get(obj); } }
使用时通过工厂创建对象:YourObject obj = ObjectCreationTracker.createAndTrack(YourObject::new);,之后调用getCreatorThreadId就能获取对应的线程ID。这里用WeakHashMap是为了避免内存泄漏——当对象被GC回收时,对应的条目会自动从Map中移除。
3. 字节码增强实现全链路无侵入追踪
如果需要完全不修改业务代码就能追踪所有对象的创建线程,可以用ASM、ByteBuddy或Javassist这类字节码操作库,在类加载时自动修改构造方法的字节码,插入记录线程ID的逻辑。比如用ByteBuddy的简单实现:
import net.bytebuddy.ByteBuddy; import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.implementation.MethodDelegation; import net.bytebuddy.matcher.ElementMatchers; import java.util.WeakHashMap; import java.util.Map; public class ThreadTrackingAgent { private static final Map<Object, Long> objectThreadMap = new WeakHashMap<>(); public static void install() { ByteBuddyAgent.install(); new ByteBuddy() .rebase(ElementMatchers.any()) .constructor(ElementMatchers.isConstructor()) .intercept(MethodDelegation.to(ThreadTrackingAgent.class)) .make() .load(ThreadTrackingAgent.class.getClassLoader()); } public static void trackCreation(Object obj) { objectThreadMap.put(obj, Thread.currentThread().getId()); } }
这种方案的优势是完全无侵入,但实现复杂度较高,还要考虑性能开销和JDK版本兼容性问题,适合对侵入性要求极高的场景。
关键提醒:JVM本身不存储对象创建线程信息
你提到反射找不到相关字段是完全正确的——JVM默认不会记录对象的创建线程,所有可行方案本质上都是在对象创建的瞬间主动记录线程ID,再通过某种方式关联到对象本身。不存在从JVM原生数据结构中直接读取该信息的途径。
内容的提问来源于stack exchange,提问作者modesitt

