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

单例模式—不要冒充我! 我就是我 是颜色不一样的烟火 天空开阔 要做最坚强的泡沫。——《我就是我》-张国荣有人冒充我给大家说一个秘密了,其实我和设计模式本来并不认识,是相识于网络上,我们聊的很多,聊人生聊梦想,有一天我突然说,设计模式我们一起去旅行吧,她说可以啊!所以才有着一次的旅行。但是总有一些人想要冒充我,因为他们看到了我和设计模式的这场旅行,那么怎么保证“设计…

大家好,又见面了,我是全栈君。

单例模式—不要冒充我!

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

有人冒充我

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

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

这个我在真实的世界只有一个(在目前看还是,以后科技发展那谁知道呢),但是在计算机的世界里,在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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/121102.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 螃蟹学PHP设计模式之中介者模式

    螃蟹学PHP设计模式之中介者模式

    2021年8月30日
    59
  • 装饰者设计模式(java版本)

    装饰者设计模式(java版本)

    2021年8月3日
    70
  • 一、设计模式-开篇—为什么我要去旅行? #和设计模式一起旅行#

    独学而无友,则孤陋而寡闻。——《礼记·学记》写在开篇,本篇是一个综合帖,里面可能会记录一些我的学习感受,也可能记录一些我学习的资料的说明,总之这就是一个大杂烩的博文。开篇杂谈最近学习一些技术之外的其他东西,怎么进行时间管理了,怎么坚持去做一件事情了,还是学到了一些其他的新的东西!做一件事情最难的是什么,是启动!启动之后最难是什么,是坚持!很多时候大的道理我们都懂,但…

    2022年2月27日
    51
  • 从零开始的Android:常见的UI设计模式「建议收藏」

    从零开始的Android:常见的UI设计模式「建议收藏」尽管Android允许您创建几乎任何可能需要的自定义视图或用户界面,但事实证明,在正确的情况下,有一些用户界面模式可以很好地适用于用户。在本教程中,您将学习其中的一些模式,以及它们如何通过在使用应用程序时创造出色的体验来帮助用户。1.主画面用户在打开应用程序时看到的第一个屏幕通常是最重要的。从这里开始,您的用户应该能够执行快速动作并继续前进,或者进一步深入到您的应用中以完善他们…

    2022年6月21日
    28
  • JAVA设计模式之享元模式

    解释一下概念:也就是说在一个系统中如果有多个相同的对象,那么只共享一份就可以了,不必每个都去实例化一个对象。比如说一个文本系统,每个字母定一个对象,那么大小写字母一共就是52个,那么就要定义52个对象。如果有一个1M的文本,那么字母是何其的多,如果每个字母都定义一个对象那么内存早就爆了。那么如果要是每个字母都共享一个对象,那么就大大节约了资源。  在Flyweight模式中,由于要产生各种各样

    2022年3月11日
    48
  • 抽象工厂设计模式例题_什么是抽象工厂模式

    抽象工厂设计模式例题_什么是抽象工厂模式定义:为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类。类型:创建类模式类图:抽象工厂模式与工厂方法模式的区别       抽象工厂模式是工厂方法模式的升级版本,他用来创建一组相关或者相互依赖的对象。他与工厂方法模式的区别就在于,工厂方法模式针对的是一个产品等级结构;而抽象工厂模式则是针对的多个产品等级结构。在编程中,通常一个产品结构,表现为一个接口或者抽

    2025年7月3日
    3

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号