有没有一种安全的方法可以使用Cleaner来注销侦听器



我有一个Swing操作类,它的工作原理如下:

package org.trypticon.hex.gui.datatransfer;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.FlavorListener;
import java.awt.event.ActionEvent;
import javax.annotation.Nonnull;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.TransferHandler;
import org.trypticon.hex.gui.Resources;
import org.trypticon.hex.gui.util.FinalizeGuardian;
import org.trypticon.hex.gui.util.FocusedComponentAction;
public class PasteAction extends FocusedComponentAction {
private final FlavorListener listener = (event) -> {
// this method in the superclass calls back `shouldBeEnabled`
updateEnabled();
};
@SuppressWarnings({"UnusedDeclaration"})
private final Object finalizeGuardian = new FinalizeGuardian(() -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.removeFlavorListener(listener);
});
public PasteAction() {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.addFlavorListener(listener);
}
@Override
protected boolean shouldBeEnabled(@Nonnull JComponent focusOwner) {
TransferHandler transferHandler = focusOwner.getTransferHandler();
if (transferHandler == null) {
return false;
}
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
DataFlavor[] flavorsInClipboard = clipboard.getAvailableDataFlavors();
return transferHandler.canImport(focusOwner, flavorsInClipboard);
}
@Override
protected void doAction(@Nonnull JComponent focusOwner) throws Exception {
Action action = TransferHandler.getPasteAction();
action.actionPerformed(new ActionEvent(
focusOwner, ActionEvent.ACTION_PERFORMED, (String) action.getValue(Action.NAME)));
}
}

这里所指的FinalizeGuardian当前使用finalize():来实现

package org.trypticon.hex.gui.util;
public final class FinalizeGuardian {
private final Runnable cleanupLogic;
public FinalizeGuardian(Runnable cleanupLogic) {
this.cleanupLogic = cleanupLogic;
}
@Override
protected final void finalize() throws Throwable {
try {
cleanupLogic.run();
} finally {
super.finalize();
}
}
}

因此,出于显而易见的原因,我想切换到使用Cleaner

第一次尝试是这样的:

package org.trypticon.hex.gui.util;
import java.lang.ref.Cleaner;
public final class FinalizeGuardian {
private static final Cleaner cleaner = Cleaner.create();
public FinalizeGuardian(Runnable cleanupLogic) {
cleaner.register(this, cleanupLogic);
}
}

问题是,现在物体永远不会变成幻影可及,因为:

  • Cleaner本身强烈引用了cleanupLogic
  • cleanupLogic保存对listener的引用,以便删除侦听器
  • listener保存对操作类的引用,以便对其调用updateEnabled
  • 操作类保存对FinalizeGuardian的引用,这样就不会过早地收集它

因为FinalizeGuardian本身永远不会变为幻影可达,所以永远不会调用清洁器。

因此,我想知道的是,是否有一种方法可以对其进行重组,以遵循使Cleaner正确工作所需的规则,即不会通过将侦听器移动到我的操作类之外来破坏封装?

只要FlavorListener在事件源处注册,它就永远不会变得不可访问(只要事件源仍然可访问(。这意味着侦听器更新的PasteAction实例也永远不会变得不可访问,因为侦听器对它有很强的引用

解耦其可达性的唯一方法是更改侦听器,只保留对其更新的对象的弱引用。请注意,当使用Cleaner而不是finalize()时,FinalizeGuardian已过时。

代码看起来像

public class PasteAction extends FocusedComponentAction {
static FlavorListener createListener(WeakReference<PasteAction> r) {
return event -> {
PasteAction pa = r.get();
if(pa != null) pa.updateEnabled();
};
}
private static final Cleaner CLEANER = Cleaner.create();
static void prepareCleanup(
Object referent, Clipboard clipboard, FlavorListener listener) {
CLEANER.register(referent, () -> clipboard.removeFlavorListener(listener));
}
public PasteAction() {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
FlavorListener listener = createListener(new WeakReference<>(this));
clipboard.addFlavorListener(listener);
prepareCleanup(this, clipboard, listener);
}
…

请注意,关键部分已被放入static方法中,以使this参考的意外捕获成为不可能。这些方法获得了完成其工作所需的最小值,createListener只接收到对该操作的弱引用,prepareCleanup将引用作为Object,因为清理操作不能访问该操作的任何成员,而是接收作为单独参数的必要值。

但在展示了更清洁的使用可能是什么样子之后,我不得不强烈反对使用这种机制,尤其是作为唯一的清洁机制。在这里,它不仅会影响内存消耗,还会影响程序的行为,因为只要引用没有被清除,侦听器就会不断得到通知,并不断更新过时的对象。

由于垃圾收集器只由内存需求触发,因此它完全有可能不运行或不关心这几个对象,因为有足够的可用内存,而CPU负载过重,因为许多过时的侦听器正忙于更新过时的对象(我在实践中见过这样的场景(。

更糟糕的是,对于并发垃圾收集器,它们的收集周期甚至可能与侦听器触发的updateEnabled()的实际过时执行重复重叠(因为引用尚未清除(。这将积极防止这些对象的垃圾收集,即使垃圾收集器运行并以其他方式收集它们也是如此。

简而言之,这样的清理不应该依赖于垃圾收集器。

相关内容

最新更新