zoukankan      html  css  js  c++  java
  • 设计模式之单例模式(二)

    上一篇我们对经典的单例模式进行了学习,并且知道了单例模式的概念,以及如何通过单线程去创建一个有效的单例模式,让程序不用多次去创建实例。

    但是,通过巧克力工厂的实践,我们很想知道在多线程模式下,这个到底会是什么情况呢?所以,就有了我们继续学习的目标啦。原来单例模式,不简单呀。

    多线程的麻烦

    首先,我们还是看下巧克力工厂经典单例的代码:原本在单线程模式下,运行的还是挺好的,工厂里那些小心翼翼的代码都可以去掉了;但是忽然用了多线程,发现还是创建了多个实例,当工厂很郁闷。

    public static ChocolateBoiler getInstance() {
    	if (uniqueInstance == null) {
    		uniqueInstance = new ChocolateBoiler(;)
    	}
    	return uniqueInstance;
    }
    

    仔细查看代码,你知道为什么了吗?这里有两个线程都要执行这段代码,那么Java的JVM在进入代码的时候肯定会有先后顺序,有没有可能是JVM搅乱了代码,让getInstance()方法内部出了问题呢?对的,就是这样。请看下图:

    当在多线程环境下,线程1和线程2都进入了getInstance方法,那么,此时通过判断null的方式来,势必在JVM内部,发生了交叉的事情,然后,然后你的工厂就创建了两个实例,挺悲剧的。

    处理多线程

    熟悉Java的朋友都知道,最轻易地解决这个多线程的方法就是把getInstantce()变成synchronized方法,比如:

    public static synchronized Singleton getInstance() {
        if(uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    

    通过增加synchronized关键字到getInstance()方法中,每个线程在进入这个方法之前,需要先等候别的线程离开该方法,也就是说,不会有两个线程可以同时进入这个方法。

    但是,问题来了:我们其实只有第一次执行此方法时,才真正需要同步。换句话说,一旦设置好uniqueInstance变量,就不需要同步这个方法了。之后每次调用这个方法,如果还是同步进行的话,给资源造成了很大的浪费,也是一种累赘。

    能改善多线程吗?

    为了符合大多数Java应用程序、我们还是需要确保单例模式能在多线程的情况下正常工作的。但是同步的getInstance()的做法将拖垮性能,该怎么办呢?

    1. 如果getInstance()的性能对应用程序不是很关键,就什么都别做

    没错,如果你的应用程序可以接受getInstance()造成的额外负担,就忽略了吧。同步getInstance()的方法既简单又有效。但是你必须知道,同步一个方法可能造成程序执行效率下降100倍。所以,还是得好好考虑应用场景哦。

    1. 使用“急切”创建实例,而不用延迟实例化的做法

    如果应用程序总是创建并使用单例实例,或者在创建和运行时方面的负担不太繁重,你可能想要急切(eagerly)创建此单例,比如:

    public class Singleton {
        // 在静态初始化中创建单例,这段代码保证了线程安全
        private static Singleton uniqueInstance = new Singleton();
        
        private Singltton() {}
        
        public static Singleton getInstance() {
            return uniqueInstance;
        }
    }
    

    利用这个做法,我们依赖JVM在加载这个类时马上创建此唯一的单例实例。JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建。哈哈,有没有,这就是饿汉式。

    1. 用双重检查加锁,在getInstance()中减少使用同步

    利用双重检查加锁(double-checked locking),首先检查是否实例已经创建了,如果尚未创建,才进行同步。这样 依赖,只有第一次会同步,这正是我们想要的。

    public class Singleton {
        private volatile static Singleton uniqueInstance;
        
        private Singleton() {}
        
        public static Singleton getInstance() {
        // 检查实例,如果不存在,就进入同步区块,只有第一次才彻底执行这里的代码
            if (uniqueInstance == null) {
                synchronized (Singleton.class) {
        // 进去区块后,再检查一次,如果仍是null,才创建实例
                    if (uniqueInstance == null) {
                        uniqueInstance = new Singleton();
                    }
                }
            }
            return uniqueInstance;
        }
    }
    

    volatile关键词确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确地处理uniqueInstance变量。

    因为单例模式大家用的比较多,而且思路也比较清晰了,那我就在这里把这章总结也一并附上了。

    设计箱内的工具

    还是按照之前的套路,总结下工具箱内新增的工具吧

    • OO基础

      抽象、封装、继承、多态

    • OO原则

      封装变化

      多用组合,少用继承

      针对接口编程,不针对实现编程

      为交互对象之间的松耦合设计而努力

      依赖抽象,不要依赖具体类

      类应该对扩展开放,对修改关闭

    • OO模式

      『策略模式』、『观察者模式』、『装饰者模式』、『抽象工厂模式』、『工厂方法模式』

      单例模式』确保一个类只有一个实例,并提供全局访问点

    好的,很开心有没有,又学会了一个设计模式,还是我们经常使用的设计模式之一。下一次,我们将学习命令模式,和大家不见不散。

    爱生活,爱学习,爱感悟,爱挨踢

  • 相关阅读:
    解决ASP.NET MVC3与FusionCharts乱码问题
    php中sprintf与printf函数用法区别
    常用的CSS缩写语法一些方法小结
    linux中利用iptables+geoip过滤指定IP
    php中magic_quotes_gpc对unserialize的影响
    mysql中权限参数说明
    jquery实现点击页面空白隐藏指定菜单
    asp.net中导出excel数据的方法汇总
    SQL取某个字段最大(小)数值及其相应行的其他字段值的句语
    Dos下查询关闭端口的命令例子
  • 原文地址:https://www.cnblogs.com/dimple91/p/10831227.html
Copyright © 2011-2022 走看看