侦听器的优缺点作为WeakReferences

如何解决侦听器的优缺点作为WeakReferences

|| 将侦听器保留为WeakReferences的利弊是什么? 大的“专业版”当然是: 将侦听器添加为WeakReference意味着该侦听器不需要麻烦自己“删除”。 更新资料 对于那些担心侦听器仅引用该对象的人,为什么不能有2种方法addListener()和addWeakRefListener()? 那些不关心清除的人可以使用后者。     

解决方法

        首先,在侦听器列表中使用WeakReference将为您的对象提供不同的语义,然后使用硬引用。在硬引用情况下,addListener(...)表示\“将特定事件通知给提供的对象,直到我用removeListener(..)明确停止它为止”;在弱引用情况下,它表示\“将有关特定事件的通知给对象特定事件,直到该对象将不会被其他任何人使用(或通过removeListener明确停止)\“。请注意,在许多情况下,拥有对象,侦听某些事件并且没有其他引用可将其与GC隔离是完全合法的。记录器可以是一个示例。 如您所见,使用WeakReference不仅可以解决一个问题(“我应该记住不要忘记在某个地方删除添加的监听器”),而且还会引发另一个问题-“我应该记住我的监听器可以停止请在没有参考的任何时候收听。”您不能解决问题,而只是将一个问题换成另一个。看,您被迫以任何一种方式明确定义,设计和跟踪收听者的寿命。 因此,就我个人而言,我同意提及,在侦听器列表中使用WeakReference更像是一种技巧,而不是解决方案。值得一提的是它的模式,例如,它有时可以为您提供帮助-使遗留代码正常工作。但这不是选择的模式:) 附言还应注意,WeakReference是什么引入了附加的间接级别,在某些情况下事件发生率极高时,间接级别可能会降低性能。     ,        这不是一个完整的答案,但是您引用的优势也可能是其主要缺点。考虑一下,如果动作监听器实施得很弱会发生什么情况:
button.addActionListener(new ActionListener() {
    // blah
});
该动作监听器随时会收集垃圾!对匿名类的唯一引用是您要向其添加事件的情况并不少见。     ,        我已经看到大量的代码,其中未正确注销侦听器。这意味着它们仍然被不必要地调用来执行不必要的任务。 如果只有一个类依赖于侦听器,那么很容易清除,但是当25个类依赖于侦听器时会发生什么呢?正确注销它们变得更加棘手。事实是,您的代码可以从引用您的侦听器的一个对象开始,并在以后的版本中可以包含25个引用同一侦听器的对象。 不使用ѭ1等效于冒消耗不必要的内存和CPU的巨大风险。它更复杂,更棘手,并且需要在复杂代码中使用硬引用进行更多工作。
WeakReferences
充满优点,因为它们会自动清理。唯一的缺点是您一定不要忘记在代码的其他地方保留硬引用。通常,这将依赖于该侦听器。 我讨厌创建监听器的匿名类实例的代码(正如Kirk Woll提到的那样),因为一旦注册,就不能再注销这些监听器。您没有对它们的引用。恕我直言,这真的很糟糕。 当您不再需要对侦听器的引用时,也可以将其“ 3”引用。您无需再为此担心。     ,确实没有专业人士。弱引用通常用于“可选”数据,例如您不想阻止垃圾收集的缓存。您不希望收集监听器垃圾,而是希望它继续监听。 更新: 好的,我想我可能已经知道您正在做什么。如果将短期侦听器添加到长期对象,则使用弱引用可能会有好处。因此,例如,如果您要向域对象添加PropertyChangeListeners以更新不断重新创建的GUI的状态,则域对象将保留在可能会建立的GUI上。考虑一个不断被重新创建的大型弹出对话框,其中一个监听器引用通过PropertyChangeListener返回到Employee对象。如果我错了,请纠正我,但是我认为整个PropertyChangeListener模式不再非常流行。 另一方面,如果您正在谈论GUI元素之间的侦听器或让域对象侦听GUI元素,那么您将不会购买任何东西,因为当GUI消失时,侦听器也会消失。 这是一些有趣的读物: http://www.javalobby.org/java/forums/t19468.html 如何解决摆动侦听器内存泄漏?     ,        坦白说,我并不是真的会接受这个想法,也不会真正希望您使用addWeakListener做些什么。也许只有我一个人,但这似乎是一个错误的好主意。起初它很诱人,但它可能暗示的问题却可以忽略不计。 使用weakReference,您不能确定在不再引用侦听器本身时是否将不再调用该侦听器。垃圾收集器可以在几毫秒后释放内存,或者永远不会释放。这意味着它可能会继续消耗CPU,并使诸如抛出异常之类的异常变得奇怪,因为不应调用侦听器。 swing的一个示例是尝试做只有在UI组件实际上已附加到活动窗口的情况下才能做的事情。这可能会引发异常,并影响通知程序,使其崩溃并阻止对有效的侦听器进行通知。 如前所述,第二个问题是匿名侦听器,它们可能过早被释放而从未通知过,也可能只有几次。 您试图实现的目标很危险,因为当您停止接收通知时,您将无法控制。它们可能会永远持续下去或停止得太早。     ,        因为假设您要添加WeakReference侦听器,所以您正在使用自定义的Observable对象。 在以下情况下,对对象使用WeakReference是非常有意义的。 -可观察对象中有一个侦听器列表。 -您已经很难参考其他地方的听众。 (您必须对此有所确定) -您不希望垃圾收集器仅因为Observable中有对它的引用而停止清除侦听器。 -在垃圾回收期间,将清除侦听器。在通知侦听器的方法中,您从通知列表中清除WeakReference对象。     ,        我想不出将WeakReferences用于侦听器的任何合法用例,除非您的用例某种程度上涉及到下一个GC周期后明确不应该存在的侦听器(该用例当然是特定于VM /平台的) )。 可以为SoftReferences设想一个稍微合法的用例,在该用例中,侦听器是可选的,但会占用大量堆,并且在可用堆大小开始变小时应首先使用。我想,某种可选的缓存或其他类型的辅助侦听器可能是候选方法。即使那样,似乎您还是希望监听器的内部使用SoftReference,而不是监听器和被监听者之间的链接。 但是,通常,如果您使用的是持久性侦听器模式,则侦听器是非可选的,因此提出此问题可能是您需要重新考虑体系结构的症状。 这是学术问题,还是您要解决的实际情况?如果是实际情况,我很想听听它是什么,并且您可能会获得更多,更少的关于如何解决它的抽象建议。     ,我认为在大多数情况下,这是个好主意。负责释放侦听器的代码在注册它的地方。 在实践中,我看到了很多软件可以永久保留收听者的声音。通常,程序员甚至都不知道应该注销它们。 通常有可能返回一个带有对侦听器的引用的自定义对象,该引用允许操纵何时注销。例如:
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     ,        我对原始海报有3条建议。很抱歉,您需要重新使用旧线程,但是我认为我的解决方案以前并未在该线程中讨论。 第一, 考虑遵循JavaFX库中的javafx.beans.values.WeakChangeListener示例。 第二, 我通过修改Observable的addListener方法来提升JavaFX模式。现在,新的addListener()方法为我创建了相应的WeakXxxListener类的实例。 可以轻松修改\“ fire event \”方法以取消引用XxxWeakListener并在WeakReference.get()返回null时将其删除。 由于我需要遍历整个列表,因此remove方法现在有点麻烦了,这意味着我需要进行同步。 第三, 在实施此策略之前,我采用了一种可能会有用的不同方法。 (硬引用)侦听器收到一个新事件,他们对是否仍在使用进行了现实检查。如果不是,则它们从观察者中退订,从而可以对其进行GC。对于寿命短的侦听器,如果订阅了寿命长的Observable,则检测过时是相当容易的。 为了尊重那些规定总是退订您的监听器的良好编程习惯,每当一个监听器诉诸取消订阅时,我确保创建一个日志条目,并在以后的代码中更正此问题。     ,        如果您特别希望GC控制侦听器的生存期,则WeakListeners很有用。 如前所述,与通常的addListener / removeListener情况相比,这确实是不同的语义,但是在某些情况下有效。 例如,考虑一棵非常大的树,该树是稀疏的-某些级别的节点未明确定义,但可以从层次结构中更深的父节点推断出来。隐式定义的节点侦听已定义的那些父节点,以使它们的隐式/继承值保持最新。但是,树很大-我们不希望隐含节点永远存在-只要调用代码使用它们,再加上可能需要几秒钟的LRU缓存,以避免一遍又一遍地搅动相同的值。 在这里,弱监听器使子节点可以侦听父节点,同时其寿命也由可达性/缓存决定,因此该结构不会在内存中保留所有隐含节点。     ,        如果您要在无法保证每次都调用的地方取消注册,则可能还需要使用WeakReference实现监听器。 我似乎还记得我们在ListView的行视图中使用的自定义
PropertyChangeSupport
侦听器之一遇到了一些问题。我们找不到注销这些监听器的好方法和可靠的方法,因此使用WeakReference监听器似乎是最干净的解决方案。     ,        从测试程序看来,匿名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
    

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-