四、单例模式—不要冒充我,我只有一个! #和设计模式一起旅行#

单例模式—不要冒充我!

我就是我 是颜色不一样的烟火
天空开阔 要做最坚强的泡沫。——《我就是我》-张国荣

有人冒充我

给大家说一个秘密了,其实我和设计模式本来并不认识,是相识于网络上,我们聊的很多,聊人生聊梦想,有一天我突然说,设计模式我们一起去旅行吧,她说可以啊!所以才有着一次的旅行。

但是总有一些人想要冒充我,因为他们看到了我和设计模式的这场旅行,那么怎么保证“设计模式”一定是和我一起旅行呢?

这个我在真实的世界只有一个(在目前看还是,以后科技发展那谁知道呢),但是在计算机的世界里,在Java的世界的,可以通过new 产生多个和我一样表现的对象,那么设计模式怎么知道那个是真正的我呢,假如她和其他人去旅行了,那我策划的这一切不就泡汤了吗!(在Java的世界里通过new 的确生成了许多个一样的对象,但是真实的情况每个对象在内存空间中有自己唯一的地址。)

所以我保证不管怎么冒充我(进行实例化),我都能只是一个,对应计算机内存空间只有一个唯一的我。

如下例子:

/**
 * 总有刁民想冒充朕!
 *
 * @author:dufyun
 * @version:1.0.0
 * @date 2018/5/20
 */
public class ManyMeTest {

    public static void main(String[] args) {

        Pattern pattern = new Pattern();
        pattern.setName("波涛汹涌的PatternMM");
        //真实的我
        Me me = new Me();
        me.setName("dufyun");
        //我们约定在520这一天乘坐飞机出发
        me.setTravelTime("2018-05-20 12:00:00");
        //假冒我的人,冒牌
        Me fakeMe = new Me();
        fakeMe.setName("fakeDufyun");
        fakeMe.setTravelTime("2018-05-20 10:00:00");

        //假冒的我先带着MM走了
        fakeMe.travel(pattern);
        //到了12点我去飞机场等MM,发现MM离开了!我有点忧伤!
        me.travel(pattern);
        /*
        fakeDufyun带着,波涛汹涌的PatternMM在2018-05-20 10:00:00出发了!
        MM已经离开了!
         */

    }
    static class Me{
        private String name;
        private String travelTime;
        private static Boolean state = true;

        //省略get
        public void setName(String name) {
            this.name = name;
        }

        //省略get
        public void setTravelTime(String travelTime) {
            this.travelTime = travelTime;
        }

        public void travel(Pattern pattern) {

            if(state){
                state = false;
                System.out.println(this.name + "带着," + pattern.getName() + "在" + this.travelTime + "出发了!");
            }else{
                System.out.println("MM已经离开了!");

            }
        }
    }
    static class Pattern{

        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }

}

上面这个例子的话就不是Me就不是单例了,那么这时候就出问题了。设计模式MM和假冒的我去旅行了。这个例子不是特别准确我为了好理解简单写的,不要深究!

要让一个类不能new 出多的实例,首先设置它的构造方法为私有的(限制外部通过new创建实例),这样在类的内部提供一个可以供外部获取实例的方法,在这个方法中进行实例的初始化,或者在类的加载的时候就初始化类的实例,并通过方法去获取实例!不管哪种方式实现,我们都一定要考虑安全性和性能!

扩展:单例有什么好处呢?

  • ①上面我们通过一个简单的例子,说明了如果Me不是单例的话,就会有问题,对应在程序中的话就会出现异常情况,导致程序不正常运行!这是不容许的!
  • ②比如Windows的任务管理,菜单上多次点击“启动任务管理器”。只能启动一个!如果启动多个会有什么问题呢?(多个窗口显示不一样,容易误导;重复打开也会消耗系统资源!)
  • ③有些对象我们只要一个,如线程池、缓存…

总结:一、多例会出现问题!二、多例浪费系统资源!

下面进入单例模式的讲解!

单例模式

概念

单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局的访问点。(有些书称为单件模式,单态模式)

  • 某个类自己管理一个单独的实例,同时避免其他类在自行产生实例!
  • 提供这个实例的全局访问点,需要实例的时候,问这个类要,它会返回单个实例。

简单点说:这个类只能有一个实例,这个实例必须它自己进行创建,然后向整个系统提供这个实例

类图

下面是单例模式的类图,单例模式的类图是比较简单的,只有一个类!

单例模式-类图

实现方式

下面一些单例模式的实现方式,感谢那些前辈,能够设计出越来越好的单例模式!

饿汉模式实例单例 - 线程安全
/**
 * 饿汉模式实例单例
 *
 * @author:dufyun
 * @version:1.0.0
 * @date 2018/5/20
 */
public class HungrySingleton {

    private  static  HungrySingleton singleton = new HungrySingleton();

    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        return singleton;
    }

    public static void main(String[] args) {
        HungrySingleton hug1 = HungrySingleton.getInstance();
        HungrySingleton hug2 = HungrySingleton.getInstance();
        System.out.println(hug1 == hug2); //true
    }
}
懒汉模式 实例单例 - 线程不安全
/**
 * 懒汉模式实现单例模式
 *
 * @author:dufyun
 * @version:1.0.0
 * @date 2018/5/20
 */
public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton(){}

    private static LazySingleton getInstance(){

        if(instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }

    public static void main(String[] args) {
        LazySingleton la1 = LazySingleton.getInstance();
        LazySingleton la2 = LazySingleton.getInstance();

        System.out.println(la1 == la2); //true
    }
}
总结一下饿汉和懒汉实现

懒汉模式其实比饿汉模式更好一点,是需要的时候才初始化实例!
其中请记住,在时间和效率上,我们经常用的无非是时间换空间或者空间换时间!
但是这两种方式实现目前看是没有问题,但是在多线程的情况下就会出现问题!

于是我们有了下面的实现方式。

/**
 * 同步方法实现单例模式 - 线程安全
 *
 * @author:dufyun
 * @version:1.0.0
 * @date 2018/5/20
 * @update:[日期YYYY-MM-DD] [更改人姓名][变更描述]
 */
public class SynchMethodSingleton {

    private static SynchMethodSingleton instance;

    private SynchMethodSingleton(){}

    public static synchronized SynchMethodSingleton getInstance(){

        if(instance == null){
            instance = new SynchMethodSingleton();
        }
        return instance;
    }

    static class MyThread extends Thread{

        @Override
        public void run() {
            System.out.println(SynchMethodSingleton.getInstance().hashCode());

        }

        public static void main(String[] args) {
            MyThread[] sp = new MyThread[10];
            for (int i = 0; i < sp.length; i++) {
                sp[i] = new MyThread();
            }
            for (int i = 0; i < sp.length; i++) {
                sp[i].start();
            }
        }
    }

}

这种方虽能解决多线程问题,但是在每次调用此方法的时候都要同步,在多线程时候回影响效率!所以我们需要进行改善!

双重检查加锁实例单例

/**
 * 双重检查锁实现单例模式
 *
 * @author:dufyun
 * @version:1.0.0
 * @date 2018/5/20
 */
public class DoubleCheckLockSingleton {

    private static volatile DoubleCheckLockSingleton instance = null;

    private DoubleCheckLockSingleton() {}

    /**
     * 双重检查锁
     */
    public static DoubleCheckLockSingleton getInstance(){
        if(instance == null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //锁的是代码块。效率更高
            synchronized (DoubleCheckLockSingleton.class) {
                // 为什么要进行二次检查呢,因为当线程创建实例对象并 出锁的代码块,新的线程进入后不会再次创建实例对象,保证对象是单例的
                //二次检查
                if(instance == null){
                    instance = new DoubleCheckLockSingleton();
                }
            }

        }
        return instance;
    }

    static class MyThread extends Thread{

        @Override
        public void run() {
            System.out.println(DoubleCheckLockSingleton.getInstance().hashCode());
        }

        public static void main(String[] args) {
            MyThread[] sp = new MyThread[200];
            for (int i = 0; i < sp.length; i++) {
                sp[i] = new MyThread();
            }
            for (int i = 0; i < sp.length; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                sp[i].start();
            }
        }
    }
}

使用双重检查的两个注意点:
- ① volatile 关键字,内存可见性、禁止指令重排序
- ② 使用synchronized 进行同步

注意:在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java5及以上的版本。

静态(static)内部类实例单例 - 线程安全

静态内部类方式可以实现延迟加载,又可以保证线程安全,不影响系统性能,是比较好的Java语言单例模式实现方式!

采用静态初始化器的方式,由JVM来保证线程的安全性!

/**
 * 静态内部类实现单例模式
 *
 * 这种模式在 序列化与反序列化中会使单例模式失效。序列号和反序列话的对象不是同一个
 * @author:dufyun
 * @version:1.0.0
 * @date 2018/5/20
 */
public class StaticInnerClassSingleton {

    private static class SingletonPatternHolder{
        private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }

    private StaticInnerClassSingleton() {}

    public static StaticInnerClassSingleton getInstance(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return SingletonPatternHolder.instance;
    }


    static class MyThread extends Thread{

        @Override
        public void run() {
            System.out.println(StaticInnerClassSingleton.getInstance().hashCode());
        }

        public static void main(String[] args) {
            MyThread[] sp = new MyThread[10];
            for (int i = 0; i < sp.length; i++) {
                sp[i] = new MyThread();
            }
            for (int i = 0; i < sp.length; i++) {
                sp[i].start();
            }
        }
    }

}

枚举实现单例 - 线程安全

用枚举来实现单例非常简单,使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用枚举来实现单例。

/**
 * 枚举实例单例模式
 *
 * @author:dufyun
 * @version:1.0.0
 * @date 2018/5/20
 */
public enum EnumSingleton {
    instance;

    public static EnumSingleton getInstance(){
        return instance;
    }

    static class MyThread extends Thread{

        @Override
        public void run() {
            System.out.println(EnumSingleton.getInstance().hashCode());
        }

        public static void main(String[] args) {
            MyThread[] sp = new MyThread[10];
            for (int i = 0; i < sp.length; i++) {
                sp[i] = new MyThread();
            }
            for (int i = 0; i < sp.length; i++) {
                sp[i].start();
            }
        }
    }

}

总结

最后,不管采取何种方案,请时刻牢记单例的三大要点:

  • 线程安全 (重要)
  • 延迟加载,性能问题 (重要)
  • 序列化与反序列化安全

注意
- 单例的实现是在同一个类加载器的作用下完成的,如果使用多个类加载器,那么生成的实例就不是单例!会导致单例失效!
- 双重检查需要注意使用JDK1.5以上版本

?Think? : 通过上面一系列的方法,可以解决被人冒充我和我的设计模式去旅行,那么你可以思考一下改进我最开始的代码,让真实的Me去和设计模式MM去飞机场,而不一个冒牌的我带走了MM!

Next :下一次要考虑旅途中的钱的问题了,请期待下一篇!

参考

本专栏文章列表

一、设计模式-开篇—为什么我要去旅行? #和设计模式一起旅行#
二、设计模式-必要的基础知识—旅行前的准备 #和设计模式一起旅行#
三、设计模式介绍—她是谁,我们要去哪里? #和设计模式一起旅行#
四、单例模式—不要冒充我,我只有一个! #和设计模式一起旅行#
五、工厂模式—旅行的钱怎么来 #和设计模式一起旅行#
六、策略模式—旅行的交通工具 #和设计模式一起旅行#
七、观察者模式——关注我,分享旅途最浪漫的瞬间! #和设计模式一起旅行#
八、装饰者模式—巴厘岛,奶茶店的困扰! #和设计模式一起旅行#
九、命令模式—使用命令控制奶茶店中酷炫的灯 #和设计模式一起旅行#
十、模板方法模式—制作更多好喝的饮品! #和设计模式一起旅行#
十一、代理模式 —专注,做最好的自己!#和设计模式一起旅行#
十二、适配器模式——解决充电的烦恼 #和设计模式一起旅行#
十三、外观模式—— 简化接口 #和设计模式一起旅行#
十四、迭代器模式—— 一个一个的遍历 #和设计模式一起旅行#
十五、组合模式—— 容器与内容的一致性 #和设计模式一起旅行#
十六、状态模式—用类表示状态 #和设计模式一起旅行#
十七、访问者模式-访问数据结构并处理数据 #和设计模式一起旅行#
十八、职责链模式-推卸责任,不关我的事,我不管!#和设计模式一起旅行#
十九、原型模式—通过复制生产实例 #和设计模式一起旅行#
二十、设计模式总结—后会有期 #和设计模式一起旅行#


如果您觉得这篇博文对你有帮助,请点赞或者喜欢,让更多的人看到,谢谢!

如果帅气(美丽)、睿智(聪颖),和我一样简单善良的你看到本篇博文中存在问题,请指出,我虚心接受你让我成长的批评,谢谢阅读!
祝你今天开心愉快!


欢迎访问我的csdn博客,我们一同成长!

不管做什么,只要坚持下去就会看到不一样!在路上,不卑不亢!

博客首页 : http://blog.csdn.net/u010648555

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 成长之路 设计师:Amelia_0503 返回首页