java设计模式之单例模式

来源:互联网 发布:淘宝怎么设置主营类目 编辑:程序博客网 时间:2024/06/10 23:37
    单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例。即一个类只有一个对象实例。--取自百度百科    当然,这不是我们今天要讨论的重点,重点是如何来实现保证系统中一个类只有一个实例,即单例模式是如何实现的呢?

实现方式一:饿汉式

package cn.zzit.singleton;/** * 饿汉式实现单例模式: * 1.构造器私有化 * 2.定义私有的静态类变量,并初始化 * 3.提供一个公开的供外部访问静态的返回类的的方法 * 优点:线程安全的,访问效率高 * 缺点:不支持延迟加载,增加性能开销 * @author yufu * */public class SingletonDemo01 {    //类初始化时,立即加载对象,加载类时,天然是线程安全的    private static SingletonDemo01 instance=new SingletonDemo01();    //构造器私有化    private SingletonDemo01(){    }    //公开的访问方法    public static SingletonDemo01 getInstance(){        return instance;    }}

饿汉式虽然实现了单例模式,但是如果创建出来的对象一直没有使用,就额外的增加了开销,所以我们就需要合理的规避这一点,因此我们接下来要使用懒汉式来实现单例模式。

实现方式二:懒汉式

package cn.zzit.singleton;/** * 懒汉式实现单例模式: 1.构造器私有化 2.定义私有的静态类变量 3.提供一个公开的供外部访问静态的返回类的的方法 优点:支持延迟加载,减少性能开销 * 缺点:线程不安全(要保证线程安全需要加synchronized进行修饰),由于要保证线程安全加synchronized修饰,访问效率相对低下 *  * @author yufu * */public class SingletonDemo02 {    //类初始化时,不初始化对象(延迟加载,真正使用的时候再创建)    private static SingletonDemo02 instance = null;    //构造器私有化    private SingletonDemo02() {    }    //公开的访问方法    public static synchronized SingletonDemo02 getInstance() {        if (instance == null) {            instance = new SingletonDemo02();        }        return instance;    }}

懒汉式解决了饿汉式的性能上的开销问题,但是,它是线程不安全的,如果要保证线程的安全,需要在方法上加synchronized锁,这样的话是保证了线程的安全,但是同时访问效率也降低了,那么我们就想了能不能把锁不加在方法上,加在方法的内部?那就要用到了双重检测锁机制。

实现方式三:双重检测锁

package cn.zzit.singleton;/** * 使用双重检测锁模式实现单例模式 * 双重检测锁模式看似综合了饿汉式和懒汉式的优点,并解决了他们的缺点,其实不然,由于编译器优化和jvm底层内部原因,偶尔会出问题,实际开发中不建议使用 *  * @author yufu * */public class SingletonDemo03 {    private static SingletonDemo03 instance = null;    private SingletonDemo03() {    }    public static SingletonDemo03 getInstance() {        // 此处判断对象是否为空,如果不为空,直接返回,提高访问效率        if (instance == null) {            // 此处如果有两条线程同时访问到此,其中一个获得锁,另一个在外等待,保证访问的安全性            synchronized (SingletonDemo03.class) {                // 如果当前持锁对象为空,把该对象初始化,之后该对象释放锁,并返回,当下一个对象访问时由于对象已经被赋值初始化,所以直接返回                if (instance == null) {                    instance = new SingletonDemo03();                }            }        }        return instance;    }}

可能有读者就会想,这就综合了懒汉与饿汉优点,完美解决了他们的缺点,其实由于编译器优化和jvm底层内部原因,双重检测锁机制偶尔会出问题,实际开发中不建议使用,那么有没有一种更好的方式呢?那就要用到静态内部类来实现。

实现方式四:静态内部类

package cn.zzit.singleton;/** * 使用静态内部类实现单例模式 * 1.外部类没有static属性,不会像饿汉式那样立即加载对象 * 2.只有真正调用getInstance,才会加载静态内部类,加载时是线程安全的, * instance是static final 类型,保证内存中只有这样一个实例,而且只能被赋值一次,从而保证了 * 线程安全性 * 3.兼备了并发高效调用和 和延迟加载的优势 * @author yufu * */public class SingletonDemo04 {    private static class SingleClassInstance{        private static final SingletonDemo04 instance=new SingletonDemo04();    }    public static SingletonDemo04 getInstance(){        return SingleClassInstance.instance;    }    private SingletonDemo04(){    }}

测试代码:

package cn.zzit.test;import cn.zzit.singleton.SingletonDemo01;import cn.zzit.singleton.SingletonDemo02;import cn.zzit.singleton.SingletonDemo03;import cn.zzit.singleton.SingletonDemo04;/** * 单例对象 占用资源少,不需要延迟加载 *   枚举式好于饿汉式 * 单例对象占用资源大,需要延迟加载 *   静态内部类好于懒汉式 * @author yufu * */public class Test {    public static void main(String[] args) throws Exception {        SingletonDemo01 s1=SingletonDemo01.getInstance();        SingletonDemo01 s2=SingletonDemo01.getInstance();        System.out.println(s1==s2);        SingletonDemo02 s3=SingletonDemo02.getInstance();        SingletonDemo02 s4=SingletonDemo02.getInstance();        System.out.println(s3==s4);        SingletonDemo03 s5=SingletonDemo03.getInstance();        SingletonDemo03 s6=SingletonDemo03.getInstance();        System.out.println(s5==s6);        SingletonDemo04 s7=SingletonDemo04.getInstance();        SingletonDemo04 s8=SingletonDemo04.getInstance();        System.out.println(s7==s8);    }}

看似上面的方式都实现了单例模式,但是其实他们都是可以通过反射和反序列化来破解的,反射破解以上单例模式的代码:

Class<SingletonDemo02> clazz=(Class<SingletonDemo02>) Class.forName("cn.zzit.singleton.SingletonDemo02");        Constructor<SingletonDemo02> c=clazz.getDeclaredConstructor(null);        c.setAccessible(true);//去除安全监测机制        SingletonDemo02 ss2=c.newInstance(null);

为了防止反射破解上述方式实现的单例模式,需要对私有的构造器进行处理:

private SingletonDemo02() {        if (instance != null) {            throw new RuntimeException();        }    }

反序列化破解上面实现的单例模式代码:

//使用反序列化破解单例模式        //使用序列化将对象写入磁盘        FileOutputStream fos=new FileOutputStream("e:/a.txt");        ObjectOutputStream oos=new ObjectOutputStream(fos);        oos.writeObject(s1);        oos.close();        fos.close();        //使用反序列化将对象从磁盘中读出        FileInputStream fis=new FileInputStream("e:/a.txt");        ObjectInputStream ois=new ObjectInputStream(fis);        SingletonDemo01 ss1=(SingletonDemo01) ois.readObject();        System.out.println(s1==ss1);

那么如何防止反序列化破解单例模式呢?代码如下:

private Object readResolve() throws ObjectStreamException{        return instance;    }

实现方式五:枚举类

package cn.zzit.singleton;/** * 通过枚举类来实现单例模式 实现方式简单,避免了其他方式使用反射和反序列化创建对象的漏洞 * 唯一的缺点就是没有懒加载,不支持延迟加载 * @author yufu * */public enum SingletonDemo05 {    //这个枚举元素,本事就是单例对象    INSTANCE;}

枚举类型实现起来比较简单,同时天然的就避免了通过使用反射和反序列化来破解单例模式,但是枚举的缺点就是没有懒加载,不支持延迟加载。
所以在实际开发中用到单例模式的,要做好合理的选择,一般情况下:如果单例对象,占用资源少,不需要延迟加载 ,枚举式好于饿汉式;如果单例对象占用资源大,需要延迟加载,静态内部类好于懒汉式。

原创粉丝点击