我有这样的课程Collector
和ThreadLocalScope
:
Collector {
Collector() {
events = new LinkedList<>();
}
add(Event e) {
events.add(e);
}
flush() {
LinkedList<Event> copy = events;
new Thread(() -> {
for (Event e : copy) {
sendToServer(e);
}
copy.clear();
).start();
events = new LinkedList<>();
}
}
ThreadLocalScope {
public static ThreadLocal<Collector> local = new ThreadLocal<>() {
protected Collector initialValue() {
return new Collector();
}
}
}
Collector
只是添加事件,并且在调用flush
时将这些事件发送到新线程中的API。 Collector
初始化为ThreadLocal。
我还有一个Job类,它被执行了几次(使用Quartz)。当这样定义时,一切都很好:
Job {
execute() {
for (int i = 0; i < 100,000; i++) {
ThreadLocalScope.get().add(new Event());
}
ThreadLocalScope.get().flush();
}
}
但是,如果我坚持像这样收集器:
Job {
Collector collector;
Job() {
collector = ThreadLocalScope.get();
}
execute() {
for (int i = 0; i < 100,000; i++) {
collector.add(new Event());
}
collector.flush();
}
}
我看到我的旧代内存使用量迅速增加,并且世界各地的垃圾收集周期一直在发生。唯一的区别是我已将Collector
添加为成员变量,而不是每次都调用ThreadLocalScope.get()
。
这一增长只能意味着将事件转移到老一代。但为什么会这样呢? Collector
会立即清除对事件的所有引用,因此即使它不是GCed,也应该是事件。
答案 0 :(得分:0)
我认为你可能会遇到线程安全问题。
不正确的。我认为这比那更简单。
在第一个版本中,您在执行作业的线程的上下文中调用ThreadLocalScope.get()。
在第二个版本中,您在创建Job()
对象的线程的contrext中调用ThreadLocalScope.get()。然后它在一个变量中被捕获,稍后在执行程序线程中使用。假设Job()
对象都是在同一个线程上创建的,这意味着您的execute()
方法共享同一个Collector对象。它们可能在不同的线程上运行。由于Collector
不是线程安全的,因此存在危险。
还有一件事您可能不知道。 Quartz可能正在使用线程池。这意味着当execute()
调用终止时,该线程将返回池中。下一次,如果Quartz使用相同的线程,它将重用上次的Collector
对象。