23种设计模式之单例模式

优点:解决对象的唯一性,确保一个类只有一个实例,节约系统资源。
缺点:由于没有抽象层,单例扩展比较困难,如果实例对象太久不被利用,系统会认为是垃圾而被回收,导致对象丢失。

单例模式主要分为以下两种:
1.饿汉式
2.懒汉式

其他的单例模式:
1.静态内部类式
2.枚举

单例模式怎么用呢?为什么要使用单例模式?解决对象的唯一性,确保一个类只有一个实例,减少内存开销,这就是单例存在的价值,看一个不是单例模式下的对象调用结果:

/**
 * 这是一个普通对象
 */
public class OrdinaryObject {
    //构造器
    public OrdinaryObject() {
    }
}

//然后使用调用者把对象new出来,看每一次是不是都是一个新对象

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //测试普通对象
        OrdinaryObject oo1 = new OrdinaryObject();
        OrdinaryObject oo2 = new OrdinaryObject();
        Log.d("TAG","oo1对象:"+oo1+"\n"+"oo2对象:"+oo2);
    }
}
image.png

结果每new一次都是一个新对象,如果这个对象只需要有一个就足够应用,还比较消耗资源的时候,这不就造成很大的资源浪费了?所以单例模式就有它存在的价值了,首先看看饿汉式怎么做,以及效果。

饿汉式(特点:调用效率高,线程安全,但是不能懒加载)

/**
 * 这是一个饿汉式单例模式
 */

public class SingletonHungry {
    //提供一个静态属性,只要类初始化,立即加载对象,不管你需不需要使用,所以被称为饿汉式,上来就要吃饭。
    private static SingletonHungry instance = new SingletonHungry();

    //私有化构造器
    private SingletonHungry(){
    }

    //提供一个可用于被外部访问的方法
    public static SingletonHungry getInstance(){
        return instance;
    }
}

这样一个饿汉式就写完了,很简单,调用试试

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       
        //测试饿汉式单例模式
        SingletonHungry s1 = SingletonHungry.getInstance();
        SingletonHungry s2 = SingletonHungry.getInstance();
        Log.d("TAG","s1对象:"+s1+"\n"+"s2对象:"+s2);
    }
}
image.png

这样就能保证对象的唯一性,无论整个程序调用多少次,在哪里调,得到的都是同一个对象,缺点也很明显,就是类已初始化就开始加载对象,也不管你是否使用,在一定的业务开发逻辑上可能无法满足,比如,我想等到我使用的时候,再去加载对象,这下可以选择使用懒汉模式。

懒汉模式(特点:调用效率低,线程安全,但是可以懒加载)

/**
 * 这是一个懒汉模式
 * <p>
 * synchronized一定要使用,如果不使用,就有可能造成创建多个对象的情况,
 * 比如线程A刚好挂起,B就直接new,B挂起,A又从挂起的地方直接new一个新对象,
 * 就会造成建立多个对象的问题
 */

public class SingletonLazy {
    //提供一个静态属性,类初始化的时候先不要加载对象,
    //等需要的时候再去加载,所以先赋null,
    //因此称为懒汉式,你叫我吃饭我才吃,要不我就不吃!
    private static SingletonLazy instance = null;

    //私有化构造器
    private SingletonLazy() {
    }

    //提供一个可用于被外部访问的方法
    public static synchronized SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

和饿汉式写法区别不大,只是不要类一初始化就把对象加载,然后多加一个判断去判断对象是否已经存在,如果为空就加载对象,否则直接把对象返回。
测试效果:

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //测试懒汉式单例模式
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        Log.d("TAG","s1对象:"+s1+"\n"+"s2对象:"+s2);
    }
}
image.png

一样能保证对象的唯一性。
这两种方式都有很明显的缺点,比如饿汉模式不可以懒加载,懒汉模式调用效率低,但是我需要的是调用效率高、线程安全、还可以懒加载,这可以使用静态内部类的方式去实现。

静态内部类实现方式(调用效率高,线程安全,可以懒加载)

/**
 * 这是一个以静态内部类实现的单例模式
 */

public class SingletonSic {

    private static class SingletonClassInstance{
        private static SingletonSic instance = new SingletonSic();
    }

    //构造器私有化
    private SingletonSic(){
    }

    //提供一个可用于被外部访问的方法
    public static SingletonSic getInstance(){
        return SingletonClassInstance.instance;
    }
}

调用测试:

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //测试静态内部类单例模式
        SingletonSic s1 = SingletonSic.getInstance();
        SingletonSic s2 = SingletonSic.getInstance();
        Log.d("TAG","s1对象:"+s1+"\n"+"s2对象:"+s2);
    }
}
image.png

一样可以保证对象的唯一性。

以上三种方式都是可以通过反射和反序列化去破解。
反射破解饿汉式测试:

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //通过反射破解单例模式
        try {
            Class<SingletonHungry> clash = (Class<SingletonHungry>) Class.forName("com.hdc.test.SingletonHungry");
            Constructor<SingletonHungry> c = clash.getDeclaredConstructor(null);
            c.setAccessible(true);

            SingletonHungry s1 = c.newInstance();
            SingletonHungry s2 = c.newInstance();
            Log.d("TAG","s1对象:"+s1+"\n"+"s2对象:"+s2);

        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
image.png

结果:对象不再是唯一性。
怎么预防(修改下构造器,当尝试第二次初始化对象的时候,直接抛出异常):

/**
 * 这是一个饿汉式单例模式
 */

public class SingletonHungry {
    //提供一个静态属性,只要类初始化,立即加载对象,不管你需不需要使用,所以被称为饿汉式,上来就要吃饭。
    private static SingletonHungry instance = new SingletonHungry();
    private static boolean flag = false;

    //私有化构造器
    private SingletonHungry(){
        synchronized(SingletonHungry.class)
        {
            if(!flag)
            {
                flag = true;
            } else
            {
                throw new RuntimeException("想侵犯我?给你抛个异常!");
            }
        }
    }

    //提供一个可用于被外部访问的方法
    public static SingletonHungry getInstance(){
        return instance;
    }
}
image.png

通过反序列化破解饿汉式:
如果需要反序列化破解先实现Serializable或者接口,比如这样:

/**
 * 这是一个饿汉式单例模式
 */

public class SingletonHungry implements Serializable {
    //提供一个静态属性,只要类初始化,立即加载对象,不管你需不需要使用,所以被称为饿汉式,上来就要吃饭。
    private static SingletonHungry instance = new SingletonHungry();

    //私有化构造器
    private SingletonHungry(){
    }

    //提供一个可用于被外部访问的方法
    public static SingletonHungry getInstance(){
        return instance;
    }
}

实现了Serializable 接口直接破解:

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        SingletonHungry s1 = SingletonHungry.getInstance();

        File file = null;
        try {
            //写入
            file = new File(this.getFilesDir().getPath() + "test.txt");
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(s1);
            oos.close();

            //读出
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
            SingletonHungry s2 = (SingletonHungry) ois.readObject();
            ois.close();

            Log.d("TAG","s1 = " + s1  + "\ns2 = " + s2);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}
image.png

结果:对象不再是唯一性。

怎么预防(在类里加个readResolve就好,其它都不需要修改):

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * 这是一个饿汉式单例模式
 */

public class SingletonHungry implements Serializable {
    //提供一个静态属性,只要类初始化,立即加载对象,不管你需不需要使用,所以被称为饿汉式,上来就要吃饭。
    private static SingletonHungry instance = new SingletonHungry();

    //私有化构造器
    private SingletonHungry(){
    }

    //提供一个可用于被外部访问的方法
    public static SingletonHungry getInstance(){
        return instance;
    }

    //防止反序列化访问
    private Object readResolve() throws ObjectStreamException{
        return instance;
    }
}

测试:


image.png

这样就能防止反序列化漏洞,保证了对象唯一性。

前面几种方式,虽然私有化构造器了,但是依然可以通过反射和反序列化去访问,但是枚举呢?枚举本身就是一个单例,而且有JVM从根本上提供保障,避免反射和序列化的漏铜,也是写法最简单的一种,如下:

枚举(调用效率高,线程安全,不可用懒加载)
/**
 * 这是一个枚举,由JVM从根本提供保障,避免反射和反序列化。
 */

public enum  SingtonEnum {
    //定义一个静态常量,
    INSTANCE
}

调用测试

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //测试枚举,直接获取两次枚举对象,看是否相等
        System.out.println(SingtonEnum.INSTANCE == SingtonEnum.INSTANCE);
    }
}
image.png

结果为true,效果是一样的,但是饿汉式,懒汉式,静态内部类式,枚举式效率都怎么样呢?测试一下看看。

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;

import java.util.concurrent.CountDownLatch;

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //饿汉式效率测试
        long startTime = System.currentTimeMillis();
        int threadNume = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNume);

        for (int i = 0; i < threadNume; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000000; i++) {
                        Object o = SingletonHungry.getInstance();
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        Log.d("TAG", "总耗时:" + (endTime - startTime));

    }
}
image.png

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;

import java.util.concurrent.CountDownLatch;

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //懒汉式效率测试
        long startTime = System.currentTimeMillis();
        int threadNume = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNume);

        for (int i = 0; i < threadNume; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000000; i++) {
                        Object o = SingletonLazy.getInstance();
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        Log.d("TAG", "总耗时:" + (endTime - startTime));

    }
}
image.png

懒汉式明显比饿汉式效率低很多

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;

import java.util.concurrent.CountDownLatch;

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //静态内部类式效率测试
        long startTime = System.currentTimeMillis();
        int threadNume = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNume);

        for (int i = 0; i < threadNume; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000000; i++) {
                        Object o = SingletonSic.getInstance();
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        Log.d("TAG", "总耗时:" + (endTime - startTime));

    }
}
image.png

静态内部类式跟饿汉式差不多

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;

import java.util.concurrent.CountDownLatch;

/**
 * 这是调用者
 */

public class MainActivity extends FragmentActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //枚举式效率测试
        long startTime = System.currentTimeMillis();
        int threadNume = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(threadNume);

        for (int i = 0; i < threadNume; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000000; i++) {
                        Object o = SingtonEnum.INSTANCE;
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }

        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        long endTime = System.currentTimeMillis();
        Log.d("TAG", "总耗时:" + (endTime - startTime));

    }
}
image.png

枚举式跟饿汉式差不多

测试完成了,开发中具体怎么选:
如果占用资源少,不需要延时加载,枚举式好于饿汉式。
如果占用资源大,需要延时加载,静态内部类式好于懒汉式。

已完成测试,有不对的地方欢迎指出,感恩。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 定义:单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中...
    holy_z阅读 360评论 0 0
  • 个人博客: http://zhangsunyucong.top 什么是单例模式 单例模式是23种设计模式中最简单和...
    长孙雨聪七星上将阅读 239评论 0 0
  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,253评论 4 34
  • 设计模式概述 在学习面向对象七大设计原则时需要注意以下几点:a) 高内聚、低耦合和单一职能的“冲突”实际上,这两者...
    彦帧阅读 3,741评论 0 14
  • 1 单例模式的动机 对于一个软件系统的某些类而言,我们无须创建多个实例。举个大家都熟知的例子——Windows任务...
    justCode_阅读 1,433评论 2 9