一、什么叫观察者模式

观察者模式是常用的设计模式之一;例如在下载文件时,我们可能会更新图标动画,另外在别的控件显示当前下载进度,下载完成后要对文件进行处理,可能这些处理过程都是不同的业务模块,这些模块的生命周期不一样。

简单来说观察者模式可以理解为对多个回调实体对象的管理;

二、简单案例

下面我们通过一个简单的例子来讲解如何设计观察者模式:
本案例是新建一个线程可以设定定时器,在时间到时发送当前的时间,最后把时间反馈给注册者。

首先我们定义回调接口

public interface ObserverCallback {public void onTime(String textTime);
}

然后在时间触发处管理回调实体

package com.penny.javademo;import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;public class Observer implements Runnable {private static final String TAG = Observer.class.getSimpleName();private volatile boolean running = false;private long interval = 1000;private List<ObserverCallback> callbacks = new CopyOnWriteArrayList<>();public Observer(long intervalMillsec) {if (interval < 1) {throw new IllegalArgumentException("interval must be larger than 0. interval: " + interval);}this.interval = intervalMillsec;}public void addCallback(ObserverCallback cb) {if (null == cb) {return;}synchronized (this) { // 防止异步线程同时find然后插入boolean find = false;for (ObserverCallback temp : callbacks) {if (cb == temp) {find = true;break;}}if (!find) {callbacks.add(cb);}}}public void removeCallback(ObserverCallback cb) {synchronized (this) {boolean find = false;for (ObserverCallback temp : callbacks) {if (cb == temp) {find = true;break;}}if (find) {callbacks.remove(cb);}}}public void initEvent() {synchronized (this) {if (! running) {new Thread(this).start();}}}public void terminate() {synchronized (this) {this.running = false;}}@Overridepublic void run() {boolean localRun = running;synchronized (this) {this.running = true;localRun = running;}DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");while (localRun) {// 处理回掉事件for (ObserverCallback cb : callbacks) {cb.onTime(df.format(Calendar.getInstance().getTime()));}try {Thread.sleep(interval);} catch (InterruptedException e) {e.printStackTrace();}synchronized (this) {localRun = running;}}synchronized (this) {this.running = false;}}
}

回调管理者,时间触发为定时发送当前时间;

@Test
public void testObserver() {final String tag = "testObserver";Observer o = new Observer(2000);o.addCallback(new ObserverCallback() {@Overridepublic void onTime(String textTime) {LogUtils.debug(tag, "111 textTime: " + textTime);}});o.addCallback(new ObserverCallback() {@Overridepublic void onTime(String textTime) {LogUtils.debug(tag, "222 textTime: " + textTime);}});o.initEvent();try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}LogUtils.debug(TAG, "testDemo finish.");
}

输出结果:

D/testObserver  111 textTime: 2019-03-16 15:11:06
D/testObserver  222 textTime: 2019-03-16 15:11:06
D/testObserver  111 textTime: 2019-03-16 15:11:08
D/testObserver  222 textTime: 2019-03-16 15:11:08
D/testObserver  111 textTime: 2019-03-16 15:11:10
D/testObserver  222 textTime: 2019-03-16 15:11:10
D/testObserver  111 textTime: 2019-03-16 15:11:12
D/testObserver  222 textTime: 2019-03-16 15:11:12
D/testObserver  111 textTime: 2019-03-16 15:11:14
D/testObserver  222 textTime: 2019-03-16 15:11:14
D/ObserverTest  testDemo finish.Process finished with exit code 0

上述代码虽然功能完善,但是存在一个问题,假如有观察对象注册了callback然后忘记注销,由于定时器一直持有该对象那么就会导致内存泄漏。

三、WeakReference加持

对于内存泄漏这个问题我们可以通过Java软引用来解决。

1. 首先用一个List来存储所有回调callback,该list用软引用WeakReference方式持有callback;

2. 用第二个List在存储强制类型的回调callback,该list直接持有callback;

3. 注册者在向定时器注册回调时可以设置注册引用类型,如果为强引用,那么步骤1、2的List都加入该对象;如果为软引用,那么只有步骤1的List会加入该对象。

4. 定时器触发事件时只需要遍历步骤1的Lit;

5. 注销某个注册者时需要遍历步骤1、2的callback,然后删除之。

package com.penny.javademo;import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;public class ObserverWeakRef3 implements Runnable {private static final String TAG = ObserverWeakRef3.class.getSimpleName();private volatile boolean running = false;private long interval = 1000;private List<WeakReference<ObserverCallback>> callbacks = new CopyOnWriteArrayList<>();private List<ObserverCallback> strongList = new CopyOnWriteArrayList<>();public ObserverWeakRef3(long intervalMillsec) {if (interval < 1) {throw new IllegalArgumentException("interval must be larger than 0. interval: " + interval);}this.interval = intervalMillsec;}public void addCallback(ObserverCallback cb, boolean isWeakRef) {if (null == cb) {return;}synchronized (this) { // 防止异步线程同时find然后插入boolean find = false;for (WeakReference<ObserverCallback> ref : callbacks) {if (cb == ref.get()) {find = true;break;}}if (!find) {callbacks.add(new WeakReference<>(cb));if (!isWeakRef) {strongList.add(cb);}}}}public void removeCallback(ObserverCallback cb) {synchronized (this) {boolean find = false;List<WeakReference<ObserverCallback>> delList = new ArrayList<>();ObserverCallback localCallback = null;for (WeakReference<ObserverCallback> ref : callbacks) {localCallback = ref.get();if (null == localCallback || cb == localCallback) {delList.add(ref);}if (cb == localCallback) {find = true;}}if (find) {strongList.remove(cb);}for (WeakReference<ObserverCallback> temp : delList) {callbacks.remove(temp);}delList.clear();}}public void initEvent() {synchronized (this) {if (! running) {new Thread(this).start();}}}public void terminate() {synchronized (this) {this.running = false;}}@Overridepublic void run() {boolean localRun = running;synchronized (this) {this.running = true;localRun = running;}ObserverCallback localCallback;DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");while (localRun) {// 处理回掉事件for (WeakReference<ObserverCallback> cb : callbacks) {localCallback = cb.get();if (null != localCallback) {localCallback.onTime(df.format(Calendar.getInstance().getTime()));} else {LogUtils.debug(TAG, "localCallback: null");}}try {Thread.sleep(interval);} catch (InterruptedException e) {e.printStackTrace();}synchronized (this) {localRun = running;}}synchronized (this) {this.running = false;}}
}

测试代码:

@Test
public void testObserverWeakRef3() {final String tag = "testObserverWeakRef3";ObserverWeakRef2 o = new ObserverWeakRef2(2000);for (int i = 0; i < 10; i++) {final String text = String.format("%d%d%d", i, i, i);o.addCallback(new ObserverCallback() {byte[] buf = new byte[1024 * 1024 * 10]; // 增大空间,诱发虚拟机回收内存@Overridepublic void onTime(String textTime) {LogUtils.debug(tag, text + " textTime: " + textTime);}}, i > 4);}o.initEvent();try {Thread.sleep(6000);System.gc(); // 手动触发gc,最终执行还是靠虚拟机的策略Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}LogUtils.debug(TAG, "testDemo finish.");
}

输出结果。

D/testObserverWeakRef3  000 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  111 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  222 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  333 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  444 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  555 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  666 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  777 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  888 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  999 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3  000 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  111 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  222 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  333 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  444 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  555 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  666 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  777 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  888 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  999 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3  000 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  111 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  222 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  333 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  444 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  555 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  666 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  777 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  888 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  999 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3  000 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3  111 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3  222 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3  333 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3  444 textTime: 2019-03-16 15:37:01
D/ObserverWeakRef3  localCallback: null
D/ObserverWeakRef3  localCallback: null
D/ObserverWeakRef3  localCallback: null
D/ObserverWeakRef3  localCallback: null
D/testObserverWeakRef3  999 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3  000 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3  111 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3  222 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3  333 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3  444 textTime: 2019-03-16 15:37:03
D/ObserverWeakRef3  localCallback: null
D/ObserverWeakRef3  localCallback: null
D/ObserverWeakRef3  localCallback: null
D/ObserverWeakRef3  localCallback: null
D/testObserverWeakRef3  999 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3  000 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3  111 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3  222 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3  333 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3  444 textTime: 2019-03-16 15:37:05
D/ObserverWeakRef3  localCallback: null
D/ObserverWeakRef3  localCallback: null
D/ObserverWeakRef3  localCallback: null
D/ObserverWeakRef3  localCallback: null
D/testObserverWeakRef3  999 textTime: 2019-03-16 15:37:05
D/ObserverTest  testDemo finish.Process finished with exit code 0

通过打印输出可以看到,通过WeakReference软引用的callback在 2019-03-16 15:37:01时就被系统回收了,因此找不到对应的callback。证明用软引用是有效的,可以避免内存泄漏;

四、总结

当然,最重要的还是调用者本身要懂得对注册者的生命周期类型,如果把原本弱周期的注册者放到定时器是使用强引用方式注册,然后又不注销,那么也会导致内存泄漏。对于这种情况我只能说——我也很无奈啊!!!

观察者模式Java内存管理进阶篇——如何避免内存泄漏相关推荐

  1. 【C语言】动态内存管理 [进阶篇_ 复习专用]

  2. Java面试题-进阶篇(2022.4最新汇总)

    Java面试题-进阶篇 1. 基础篇 1.1 基本数据类型和包装类 1.2 Double转Bigdecimal可能会出现哪些问题?怎么解决? 1.3 equals 与 == 的区别? 1.4 Java ...

  3. 读懂 JVM 内存管理这篇就够了

    读懂 JVM 内存管理这篇就够了 JVM 的内存结构 程序计数器 作用 概述 PC寄存器的常见问题 虚拟机栈 栈中可能出现的异常 栈的存储单位 栈运行原理 栈帧的内部结构 局部变量表 槽 Slot 操 ...

  4. java内存管理的一些基础,内存溢出的解决方案

    Java的内存组成:      Java的内存主要有两种:栈内存(stack)和堆内存(heap) 栈内存的优势是存取速度快,在栈中存放的变量都是在编译期就可确定其值.生命周期的,栈内存最大的一个特点 ...

  5. Linux内存管理宏观篇(七)虚拟内存

    Linux内存管理宏观篇(七)虚拟内存 前面知道了物理内存,物理内存是实打实的,我只有这么多,用的时候你只能用这么多. 为了解决一些问题,产生虚拟内存,通过虚拟内存可以让我们每个进程都能拥有虚拟的3G ...

  6. Linux内存管理宏观篇(一):不同角度去看内存(硬件)

    1.硬件角度 大家都曾经看过那个纸上打孔,记录数据的图片. 后来都知道出现了内存器,我们执行指令分为加载+运行. 最开始的程序运行时只能跑一个进程的,那就不需要复杂的内存管理,把我弄到固定的位置,然后 ...

  7. linux内存管理_浅谈Linux内存管理

    1. 扫盲篇 1.1 操作系统存储层次 常见的计算机存储层次如下: 寄存器:CPU提供的,读写ns级别,容量字节级别. CPU缓存:CPU和CPU间的缓存,读写10ns级别,容量较大一些,百到千节. ...

  8. android内存池,两种常见的内存管理方法:堆和内存池

    描述 本文导读 在程序运行过程中,可能产生一些数据,例如,串口接收的数据,ADC采集的数据.若需将数据存储在内存中,以便进一步运算.处理,则应为其分配合适的内存空间,数据处理完毕后,再释放相应的内存空 ...

  9. Spark内存管理(2)—— 统一内存管理

    Spark内存管理系列文章:  Spark内存管理(1)-- 静态内存管理 堆内内存 Spark 1.6之后引入的统一内存管理机制,与静态内存管理的区别在于Storage和Execution共享同一块 ...

  10. Spark内存管理(1)—— 静态内存管理

    Spark内存管理简介 Spark从1.6开始引入了动态内存管理模式,即执行内存和存储内存之间可以相互抢占  Spark提供了2种内存分配模式: 静态内存管理 统一内存管理 本系列文章将分别对这两种内 ...

最新文章

  1. 初学者SQL语句介绍
  2. hdc mfc 画扇形图_使用echarts绘制条形图和扇形图
  3. 软件测试培训分享:软件测试初期怎么面试工作?
  4. vue-route动态路由
  5. html点击旋转180,关于点击三角丝滑旋转180度css3 jq处理方法
  6. 图像浏览界面缩放和平移操作的实现
  7. css文件修改后没变化 static_Go Web编程使用Go语言创建静态文件服务器
  8. apache mediawiki 安装_如何在CentOS 7上安装MediaWiki
  9. Ubuntu安装LNMP
  10. python反向代理服务器_主机、服务器,代理服务器,反向代理服务器理解(自用)...
  11. 第五章 线性回归 学习笔记中
  12. qt制作2048小游戏
  13. 李航统计学习方法 Chapter1 统计学习方法概论
  14. 去掉office标题前的黑点
  15. 基于SSM学生学籍管理系统
  16. c语言lst文件,Keil C51 之LST文件
  17. OpenCV每日函数 几何图像变换模块 (8) remap函数
  18. 【kali】kali2020.2安装 超级详细教程
  19. 数据库连接池的管理思想
  20. 计算机游戏的最新技术,搭载十代酷睿i7处理器 这台ROG冰刃4新锐拥有媲美台式游戏电脑的性能...

热门文章

  1. Hadoop在master查看live nodes为0解决方案
  2. 2018_11_25_生活记录
  3. 739.每日温度 (力扣leetcode) 博主可答疑该问题
  4. android手机和荣耀哪个版本好,【求测评】荣耀v40轻奢版与荣耀X10哪款更好?图文爆料分析...
  5. java list加入listview_将卡添加到ListView
  6. Canu FAQ常见问题
  7. springmvc 使用Jackson的配置
  8. Bootstrap-1
  9. 三种GDB类型的转换后字段类型的变化
  10. 浅谈Android选项卡(二)