喵星之旅-沉睡的猫咪-单例模式

 

一、单例是什么?

单例是gof所提到的23种设计模式中的一种,属于创建型设计模式。

1、结构:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2、动机:对于某些类,只有一个实例是很重要的。尤其是某些具有高级权限的对象,比如线程池可以启动线程,这是对cpu这种极其昂贵资源的调用,单例显得尤为重要。

3、效果:对唯一实例的受控访问

4、三要素:私有的构造方法;指向自己实例的私有静态引用;以自己实例为返回值的静态的公有方法。


二、饿汉式单例

在类加载初始化的时候就主动创建实例。单例不要实现序列化接口,这里只是为后面错误演示提供方便。

package create.singleton.hungry;

import java.io.Serializable;

/**
 * 
 * @author bunny~~我是兔子我会喵,我叫喵星兔。 
 * 饿汉单例 HungrySingleton
 */
public class EhanDanli implements Serializable {

    private static final long serialVersionUID = 1L;
    private static final EhanDanli instance = new EhanDanli();

    private EhanDanli() {
    }

    public static EhanDanli getInstance() {
        return instance;
    }
}
package create.singleton.hungry;
/**
 * 
 * @author bunny~~我是兔子我会喵,我叫喵星兔。
 *
 */
public class Test {

    public static void main(String[] args) {
        EhanDanli s1 = EhanDanli.getInstance();
        EhanDanli s2 = EhanDanli.getInstance();
        EhanDanli s3 = EhanDanli.getInstance();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s1 == s2);

    }

}

 

三、懒汉式单例

等到真正使用的时候才去创建实例,不用时不去主动创建。

package create.singleton.lazy;

/**
 * 
 * @author bunny~~我是兔子我会喵,我叫喵星兔。
 *  懒汉单例 LazySingleton
 */
public class LanhanDanli {
    private LanhanDanli() {
    }
    private static LanhanDanli instance;
    public static LanhanDanli getInstance() {
        if (instance == null) {
            synchronized (LanhanDanli.class) {
                if (instance == null) {
                    instance = new LanhanDanli();
                }
            }
        }
        return instance;
    }
}
package create.singleton.lazy;

/**
 * 
 * @author bunny~~我是兔子我会喵,我叫喵星兔。
 *
 */
public class Test {

    public static void main(String[] args) {
        LanhanDanli s1 = LanhanDanli.getInstance();
        LanhanDanli s2 = LanhanDanli.getInstance();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }

}

 

四、枚举单例

jvm提供底层保证,不可能出现序列化、反射产生对象的漏洞 。
package create.singleton.register;
/**
 * 
 * @author bunny~~我是兔子我会喵,我叫喵星兔。
 * EnumSingleton
 */
public enum ZhuceDanli {
    INSTANCE;
    public static ZhuceDanli getInstance() {
        return INSTANCE;
    }
}
package create.singleton.register;

public class Test {

    public static void main(String[] args) {
        ZhuceDanli s1 = ZhuceDanli.getInstance();
        ZhuceDanli s2 = ZhuceDanli.getInstance();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }

}

 

五、单例的破坏

1、反射

单例实现的一个主要因素就是构造器私有化,但是反射可以执行私有化的构造器。

package create.singleton;

import java.lang.reflect.Constructor;

import create.singleton.hungry.EhanDanli;

/**
 * 
 * @author bunny~~我是兔子我会喵,我叫喵星兔。
 *  Reflect 反射破坏
 */
public class FanshePohuai {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = EhanDanli.class;
        Constructor c = clazz.getDeclaredConstructor(null);
        c.setAccessible(true);
        Object s1 = c.newInstance();
        Object s2 = c.newInstance();
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

 

2、序列化、反序列化

破坏原因同上。

package create.singleton;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import create.singleton.hungry.EhanDanli;

/**
 * 
 * @author bunny~~我是兔子我会喵,我叫喵星兔。
 *Seriable 序列化破坏
 */
public class XunliehuaPohuai {
    public static void main(String[] args) throws Exception {
        EhanDanli s1 = EhanDanli.getInstance();
        EhanDanli s2 = null;
        FileOutputStream fos = null;

        fos = new FileOutputStream("Seriable.bunny");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s1);
        oos.flush();
        oos.close();
        FileInputStream fis = new FileInputStream("Seriable.bunny");
        ObjectInputStream ois = new ObjectInputStream(fis);
        s2 = (EhanDanli) ois.readObject();
        ois.close();
        
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

 

六、单例的总结和选择

饿汉式单例:执行效率高,性能高,启动慢。

懒汉式单例:需要考虑锁的问题,没有启动问题,相对难度大。

注册式单例:无法被破坏的单例。实际上就是饿汉式,只不过某些人通过给jdk的官方每人饭里加了鸡腿后,让他们创建了一个霸权模式的饿汉。


重要的对象优选饿汉式。因为重要的内容终究是不多的,没必要在意那些启动时间问题。

不重要的选择懒汉式或者不建议用单例,需要考虑是否必要,因为“单例是邪恶的”。

单例的选择是一把双刃剑,我们要考虑到世界上有一种叫“客户”的动物,外号“上帝”。


对于注册式单例,有赞成使用的,因为安全,不会被破坏。本人不赞成使用。

因为一个完善的生态需要有出生、成长、消亡的过程,而不是永存。健全的生态是平等而不是独裁。如果上帝创造了一种生物,不单没有天敌,而且不会死亡,那么他创造的就是恶魔、灾难,他要有消亡的过程。如果大权永远掌握在一个人手中,那是独裁,就像墨索里尼。

就像丘吉尔和斯大林的对话。斯大林问丘吉尔:“丘吉尔先生,您打赢了仗,人民却罢免了您。丘吉尔回敬道:“斯大林先生,我打仗就是保卫让人民有罢免我的权力

而注册式单例不允许他人去罢免他,终究不是我的选择,至于如何选择,仁者见仁。我更愿意追随我最喜欢的二战英雄。


单例到底带来了什么?不是对象的唯一性,最终是受控访问,也就是控制权限,和对象的边界有着很多的联系。如果我们在开发中考虑到了权限的限制,并且想通过对象数量来控制,那就是基于单例模式的,哪怕最后出现的是2个对象。单例模式不限于它具体的代码实现和教条式的定义,只要我们心中所想和它一样,哪怕实现方式不同,那也是单例的灵魂,可能不是单例的代码。设计模式不是让我们照搬他们的代码,而且起到抛砖引玉的目的。我们是通过学习他们的设计思想、处理问题的经验,然后提升改进我们自己的代码。


ps:项目地址  svn://47.105.188.20/kitty/2%E3%80%81code/pattern    用户名密码:reader/reader

posted @ 2020-02-28 19:06  喵星兔  阅读(227)  评论(0编辑  收藏  举报