将侦听器保留为WeakReferences的利弊是什么?
当然,最大的“专业版”是:
将侦听器添加为WeakReference意味着侦听器无需费心“删除”自身。
更新资料
对于那些担心侦听器仅引用对象的人,为什么不能有2个方法addListener()和addWeakRefListener()?
那些不关心删除的人可以使用后者。
将侦听器保留为WeakReferences的利弊是什么?
当然,最大的“专业版”是:
将侦听器添加为WeakReference意味着侦听器无需费心“删除”自身。
更新资料
对于那些担心侦听器仅引用对象的人,为什么不能有2个方法addListener()和addWeakRefListener()?
那些不关心删除的人可以使用后者。
Answers:
首先,在侦听器列表中使用WeakReference将为您的对象提供不同的语义,然后使用硬引用。在硬引用情况下,addListener(...)的意思是“通知提供的对象有关特定事件,直到我用removeListener(..)明确停止为止。”在弱引用情况下,它表示“通知有关特定事件的提供对象( s),直到该对象将不会被其他任何人使用(或通过removeListener明确停止)”。请注意,在许多情况下,拥有对象,侦听某些事件并且没有其他引用可将其与GC隔离是完全合法的。记录器可以是一个示例。
如您所见,使用WeakReference不仅可以解决一个问题(“我应该记住不要忘记在某个地方删除添加的监听器”),而且还会引发另一个问题-“我应该记住,我的监听器可以在任何时候停止监听不再引用它的时刻”。您不能解决问题,而只是将一个问题换成另一个。看,您以任何一种方式必须明确定义,设计和跟踪收听者的生命周期。
因此,就我个人而言,我同意提及,在侦听器列表中使用WeakReference更像是hack,而不是解决方案。这是一个值得了解的模式,有时它可以帮助您-例如,使遗留代码正常工作。但这不是选择的模式:)
PS还应注意,WeakReference引入了额外的间接级别,在某些情况下事件率极高时,间接级别可能会降低性能。
addWeakListener(listener)
可能是一个好习惯,因为此类的用户将了解内部发生了什么事情以避免丢失通知
这不是一个完整的答案,但是您引用的优势也可能是其主要缺点。考虑一下,如果动作监听器实施得很弱,会发生什么情况:
button.addActionListener(new ActionListener() {
// blah
});
该动作监听器随时会收集垃圾!对匿名类的唯一引用是您要向其添加事件的情况并不少见。
我已经看到大量的代码,其中未正确注销侦听器。这意味着它们仍然被不必要地调用来执行不必要的任务。
如果只有一个类依赖于侦听器,那么很容易清除,但是当25个类依赖于侦听器时会发生什么呢?正确注销它们变得更加棘手。事实是,您的代码可以从引用您的侦听器的一个对象开始,并在以后的版本中可以包含25个引用同一侦听器的对象。
不使用WeakReference
就等于冒着消耗不必要的内存和CPU的巨大风险。它更复杂,更棘手,并且需要在复杂代码中使用硬引用进行更多工作。
WeakReferences
充满了优点,因为它们会自动清理。唯一的缺点是您一定不要忘记在代码的其他地方保留硬引用。通常,这将依赖于该侦听器。
我讨厌创建监听器的匿名类实例的代码(正如Kirk Woll提到的那样),因为一旦注册,就无法再注销这些监听器。您没有对它们的引用。恕我直言,这真的很糟糕。
您也可以null
在不再需要监听器时对其进行引用。您无需再为此担心。
确实没有专业人士。弱引用通常用于“可选”数据,例如您不想阻止垃圾收集的缓存。您不希望收集您的监听器垃圾,而是希望它继续监听。
更新:
好的,我想我可能已经知道您正在做什么。如果将短期侦听器添加到长期生存的对象,则可能会受益于使用weakReference。因此,例如,如果您要向域对象添加PropertyChangeListeners以更新不断重新创建的GUI的状态,则域对象将保留在可能会建立的GUI上。考虑一个不断被重新创建的大型弹出对话框,其中一个侦听器引用通过PropertyChangeListener返回到Employee对象。如果我错了,请纠正我,但我认为整个PropertyChangeListener模式不再受欢迎。
另一方面,如果您正在谈论GUI元素之间的侦听器或让域对象侦听GUI元素,那么您将不会购买任何东西,因为当GUI消失时,侦听器也会消失。
这是一些有趣的读物:
老实说,我并没有真正购买该想法,也没有真正希望您使用addWeakListener进行购买。也许只有我一个人,但这似乎是一个错误的好主意。起初它很诱人,但它可能暗示的问题却可以忽略不计。
使用weakReference,您不能确定在不再引用侦听器本身时是否将不再调用该侦听器。垃圾收集器可以在几毫秒后释放内存,或者永远不会释放。这意味着它可能会继续消耗CPU,并使诸如抛出异常之类的异常变得奇怪,因为不应调用侦听器。
swing的一个示例是尝试做只有在UI组件实际上已附加到活动窗口的情况下才能做的事情。这可能会引发异常,并影响通知程序使其崩溃并阻止对有效的侦听器进行非官方通知。
如前所述,第二个问题是匿名侦听器,它们可能过早被释放,甚至根本没有被通知或只有几次。
您试图实现的目标很危险,因为当您停止接收通知时,您将无法控制。它们可能会永远持续下去或停止得太早。
我认为在大多数情况下,这是个好主意。负责释放侦听器的代码在注册它的位置相同。
在实践中,我看到了很多软件可以永久保留收听者的声音。通常,程序员甚至都不知道应该注销它们。
通常有可能返回一个带有对侦听器的引用的自定义对象,该引用允许对何时注销的操作。例如:
listeners.on("change", new Runnable() {
public void run() {
System.out.println("hello!");
}
}).keepFor(someInstance).keepFor(otherInstance);
此代码将注册侦听器,返回一个封装侦听器并具有方法的对象,keepFor会将侦听器添加到以实例参数为键的静态weakHashMap中。这样可以确保至少在不对someInstance和otherInstance进行垃圾回收的情况下注册侦听器。
可能还有其他方法,例如keepForever()或keepUntilCalled(5)或keepUntil(DateTime.now()。plusSeconds(5))或unregisterNow()。
默认值可以永久保留(直到未注册)。
这也可以在没有弱引用但幻影引用触发侦听器删除的情况下实现。
编辑:创建一个小库,实现此方法的基本版本https://github.com/creichlin/struwwel
on()
但之前keepFor()
呢?大概那时可能已经返回的keepFor()
呼叫weakReference.get()
了null
吗?
我无法想到将WeakReferences用于侦听器的任何合法用例,除非您的用例以某种方式涉及在下一个GC周期之后明确不应该存在的侦听器(该用例当然是特定于VM /平台的)。
可以为SoftReferences设想一个稍微合法的用例,在该用例中,侦听器是可选的,但会占用大量堆,并且在可用堆大小开始变小时应首先使用。我想,某种可选的缓存或其他类型的辅助侦听器可能是候选方法。即使这样,您似乎还是希望监听器的内部组件利用SoftReferences,而不是监听器和被监听者之间的链接。
但是,通常,如果您使用的是持久性侦听器模式,则侦听器是非可选的,因此提出此问题可能是您需要重新考虑体系结构的症状。
这是学术问题,还是您要解决的实际情况?如果是实际情况,我很想听听它是什么,并且您可能会获得更多,更少抽象的解决方法的建议。
我对原始海报有3条建议。很抱歉,您需要重新使用旧线程,但是我认为我的解决方案以前并未在该线程中讨论。
首先,考虑遵循JavaFX库中的javafx.beans.values.WeakChangeListener示例。
其次,我通过修改Observable的addListener方法来提升JavaFX模式。现在,新的addListener()方法为我创建了相应的WeakXxxListener类的实例。
轻松修改了“触发事件”方法,以取消引用XxxWeakListener并在WeakReference.get()返回null时将其删除。
remove方法现在有点麻烦,因为我需要遍历整个列表,这意味着我需要进行同步。
第三,在实施此策略之前,我采用了一种可能会有用的不同方法。(硬引用)侦听器收到一个新事件,他们对是否仍在使用进行了现实检查。如果不是,则它们从观察者中退订,从而可以对其进行GC。对于寿命短的侦听器,订阅了寿命长的Observables,检测过时很容易。
为了尊重那些规定“总是取消订阅侦听器的良好编程习惯,每当侦听器诉诸取消订阅本身时,我都确保创建一个日志条目并稍后在我的代码中更正此问题。
如果您特别希望GC控制侦听器的生存期,则WeakListeners很有用。
如前所述,与通常的addListener / removeListener情况相比,这确实是不同的语义,但是在某些情况下有效。
例如,考虑一个非常大的树,该树是稀疏的-某些级别的节点未明确定义,但是可以从层次结构中更深的父节点推断出来。隐式定义的节点侦听已定义的那些父节点,以使它们的隐式/继承值保持最新。但是,树是巨大的-我们不希望隐含节点永远存在-只要调用代码使用它们,再加上可能需要几秒钟的LRU高速缓存,以避免一遍又一遍地搅动相同的值。
在这里,弱监听器使子节点可以侦听父节点,同时其寿命也由可达性/缓存决定,因此该结构不会在内存中保留所有隐含节点。
从测试程序看来,匿名ActionListeners不会阻止对象被垃圾回收:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
public class ListenerGC {
private static ActionListener al = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.err.println("blah blah");
}
};
public static void main(String[] args) throws InterruptedException {
{
NoisyButton sec = new NoisyButton("second");
sec.addActionListener(al);
new NoisyButton("first");
//sec.removeActionListener(al);
sec = null;
}
System.out.println("start collect");
System.gc( );
System.out.println("end collect");
Thread.sleep(1000);
System.out.println("end program");
}
private static class NoisyButton extends JButton {
private static final long serialVersionUID = 1L;
private final String name;
public NoisyButton(String name) {
super();
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println(name + " finalized");
super.finalize();
}
}
}
产生:
start collect
end collect
first finalized
second finalized
end program