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

一、什么叫观察者模式

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

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

 

二、简单案例


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

首先我们定义回调接口

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;
        }
    }

    @Override
    public 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() {
		@Override
		public void onTime(String textTime) {
			LogUtils.debug(tag, "111 textTime: " + textTime);
		}
	});
	o.addCallback(new ObserverCallback() {
		@Override
		public 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;
        }
    }

    @Override
    public 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]; // 增大空间,诱发虚拟机回收内存
			@Override
			public 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。证明用软引用是有效的,可以避免内存泄漏;

 

四、总结

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值