java中你的单例在裸奔吗?

星爷镇楼.

在上一篇文章java中你确定用对单例了吗?中提到单例可以被恶意的破坏,如序列化破坏和反射破坏单例的结构,好的,这个有点偏,确实在实际开发中基本也不会在意到这个问题,但是谁叫我们搞的是java,所以这个问题我们有必要知道下,这算是提高下自己的安全意识,有句古话是这样说的,居安思危嘛.

好,请带着欢乐的心情继续往下看.

通过反射破解单例结构

java中你的单例是不是一直在裸奔,估计你用的是假的单例.
我们就使用普通懒汉式来做示例吧.

public class SingletonDemo6  implements Serializable{
    private static SingletonDemo6 s1;
  //普通懒汉式写法
    public static synchronized SingletonDemo6 getInstance() {
        if (s1 == null) {
            s1 = new SingletonDemo6();
        }
        return s1;
    }

看看下面测试结果.
在正常情况下,没毛病,输出结果一毛一样.

@Test
public  void test() throws Exception{
        SingletonDemo6 nomarlInstance1 = SingletonDemo6.getInstance();
        SingletonDemo6 nomarlInstance2 = SingletonDemo6.getInstance();

//这两个单例输入的实例都是一样
System.out.println(nomarlInstance1);
System.out.println(nomarlInstance2);

log:
com.relice.singleton.SingletonDemo6@5a10411
com.relice.singleton.SingletonDemo6@5a10411

当反射遇上单例

看下面反射破解单例的测试代码,输出两个不同的结果.

@Test
public  void test() throws Exception{
        SingletonDemo6 nomarlInstance1 = SingletonDemo6.getInstance();

         Class<SingletonDemo6> forName = (Class<SingletonDemo6>) Class
         .forName("com.relice.singleton.SingletonDemo6");
         Constructor<SingletonDemo6> c = forName.getDeclaredConstructor();
         //绕过权限管理,获取private
         c.setAccessible(true);
         //通过反射拿到`SingletonDemo6`的实例
         SingletonDemo6 reflectInsatnce = c.newInstance();
        
         // 两者的输出结是不一样的
         System.out.println(nomarlInstance1);
         System.out.println(reflectInsatnce);

log:
com.relice.singleton.SingletonDemo6@5a10411
com.relice.singleton.SingletonDemo6@2ef1e4fa

如何解决这种问题?

大神说遇到问题不要急,先分析问题出现的原因.

  1. forName.getDeclaredConstructor();主要就是获取无参数构造.
  2. 也就是说通过反射拿到了私有构造方法从而再次创建实例.

知道问题的原因那就好办了.
我们可以在SingletonDemo6的构造方法里做判断,避免他再次创建实例.

// 解决反射 多获取对象问题
        private SingletonDemo6() {
            if (s1 != null) {
                try {
                    throw new RuntimeException("禁止反射获取对象");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

这样如果有人想要通过反射破坏单例结构,那就会抛出运行时异常.

log:
java.lang.RuntimeException: 禁止反射获取对象 at
com.relice.singleton.SingletonDemo6.<init>(SingletonDemo6.java:24)

通过序列化破解单例结构

还是用SingletonDemo6来测试,通过序列化获取到实例,得出了两个不一样的结果.
憋说话,继续看问题.

@Test
public  void test() throws Exception{
        SingletonDemo6 nomarlInstance1 = SingletonDemo6.getInstance();
        
    //把对象写入文件
        File file = new File(
                "/xxx/xxx/xxx/xxx/xxx/SingletonDemo/a.txt");
        FileOutputStream fos = new FileOutputStream(file);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(nomarlInstance1);
        oos.close();
        fos.close();
        
        //序列化把对象读取
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = new ObjectInputStream(fis);
        SingletonDemo6 serilizeInstance = (SingletonDemo6) ois.readObject();
        
        System.out.println(nomarlInstance1);
        System.out.println(serilizeInstance);
}   

log:
com.relice.singleton.SingletonDemo6@68de145
com.relice.singleton.SingletonDemo6@27fa135a

如何解决这种问题?

老规矩我们还是先分析.

  1. 在序列化里我们可以通过流的方式将一个对象写入内存中oos.writeObject,因此也就可以将这个对象从内存中读取出来.
  2. 但是当序列化遇到单例问题就发生了,在读取对象时jvm会重新给序列化对象分配地址.
  3. 因此我们要考虑的问题就是反序列化

解决方法:
当反序列化的时候:
JVM会调用readObject方法,将我们刚刚在writeObject方法序列化好的属性,反序列化回来. 然后在readResolve方法中,我们也可以指定JVM返回我们特定的对象(不是刚刚序列化回来的对象). .该方法的分析见

private Object readResolve() throws ObjectStreamException{
     return SingletonDemo6.s1;
}

可能我们会考虑到一个问题,就是之前的反射不是在构造方法里处理解决问题吗,那是不是序列化也可以?
要知道序列化和反序列化,在java中是使用字节码技术生成对象,并不会执行构造器方法.

android开发中要注意的问题

接触过android的都知道,在其中四大组件中就有三大组件是有生命周期的,生命周期最关键的的就是context,连基本的Activty 和 Service都是从Context派生出来的,也因为这生命周期让android应用在用户体验上附上了一些生命气息,,如视频播放根据生命周期来处理播放状态;如我们想边听音乐边干些别事情,这是Service的生命周期就有帮我们做到等..

我想说的就是.
android组件中的生命周期是尤其重要,因此我们要善待context,在处理或者使用到组件的生命周期时也要注意规范,提高容错率.

Android开发 单例模式导致内存泄露

实际开发中用到最多的设计模式,如果单例设计模式认第二,我想没有敢认第一的.如工具类,application类,配置文件等.
不扯淡了,以工具类为例,存在内存泄露问题的一些代码片段像下面这样:

public class Util {

    private Context mContext;
    private static Util mInstance;

    private Util(Context context) {
        this.mContext = context;
    }

    public static Util getInstance(Context context) {
        if (mInstance == null) {
            synchronized (Util.class) {
                if (mInstance == null) {
                    mInstance = new Util(context);
                }
            }
        }
        return mInstance;
    }
}

其实实际开发中排查问题和定位问题一直是占据了大部分的工作时间,因此拥有一个好的开发方式可以减少很多不必要的时间浪费,这里有篇关于使用android studio检查内存泄漏的文章觉得不错.

分析下问题:

  1. Util.getInstance(this);这个this使用的就是Activity的context.
  2. Activity的生命周期都是比较短暂的,当用户切换页面的时候基本都会把activity销毁掉,因此贯穿整个生命周期的context类也会被相应的相会.
  3. Util.getInstance(mContext);在工具类里封装一些耗时的操作也是常见的,当Activity生命周期结束,但Util类里面却还存在A的引用 (mContext),这样Activity占用的内存就一直不能回收,而Activity的对象也不会再被使用.从而造成内存泄漏.

解决问题:

  1. 在Activity中,可以用Util.getInstance(getApplicationContext());Util.getInstance(getApplication());来代替。
    因为Application的生命周期是贯穿整个程序的,所以Util类持有它的引用,也不会造成内存泄露问题。

  2. 使用弱引用让这个引用自动被回收
    弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。

下面代码是使用了弱引用之后

public class WeakRUtil {
    private static Context mContext;
    private static WeakRUtil mInstance;

    private WeakRUtil(Context context) {
        this.mContext=context;
    }

    public static WeakRUtil getInstance(Context context) {
        if (mInstance == null) {
            WeakReference<Context> actWeakRF = new WeakReference<Context>(context);
            //通过get来获取弱引用关联对象,如果为null 则就是被回收了
            mContext = actWeakRF.get();

            synchronized (WeakRUtil.class) {
                if (mInstance == null) {
                    mInstance = new WeakRUtil(mContext);
                }
            }
        }
        return mInstance;
    }

    public void test() {
        System.out.println("util_test");
    }
}

在java中,用java.lang.ref.WeakReference类来表示。
当调用了System.gc(); 则即使内存足够,该引用内的数据都会被回收;

好了,我们继续总结下:

  1. 单例的优点就是提供了对唯一实例的受控访问,减少内存分配,提高系统性能,也因为这个优点所以我们要避免单例被恶意的破坏掉了其结构.
  2. 单例在实际开发中经常使用到,而使用方法也是各种各种,为了让代码有更好的健壮性,因此一些开发中的编程习惯要养成,避免如oom异常.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,231评论 4 34
  • 前言 本文主要参考 那些年,我们一起写过的“单例模式”。 何为单例模式? 顾名思义,单例模式就是保证一个类仅有一个...
    tandeneck阅读 2,490评论 1 8
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,213评论 11 349
  • 1.单例模式概述 (1)引言 单例模式是应用最广的模式之一,也是23种设计模式中最基本的一个。本文旨在总结通过Ja...
    曹丰斌阅读 2,889评论 6 47
  • 网站开始建设前,需要先选取关键词,并以此扩展。常用的关键词工具挖掘方法就是在百度搜索框中输入扩展关键词、SE...
    bb1286432阅读 832评论 0 0