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

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

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

/**
 * 这是一个懒汉模式
 * 

* 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 clash = (Class) Class.forName("com.hdc.test.SingletonHungry");
            Constructor 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

枚举式跟饿汉式差不多

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

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

你可能感兴趣的:(23种设计模式之单例模式)