knot

导航

设计模式三之单例模式

有一些对象,我们只需要一个,比如:线程池,缓存,对话框,处理偏好设置和注册表的对象等等。

这些对象只能有一个实例,如果制造出多个实例,就会产生bug。

全局变量和单例的区别:

(1) 全局变量必须在程序一开始就创建好对象,如果这个对象非常耗费资源,而程序在本次执行中没有用到这个变量,则会造成资源的浪费。

(2) 单例模式可以实现在需要用到的时候再创建该对象,且保证整个程序中只创建一次。

接下来,我们来分析单例模式的最经典实现:

public class Singleton {

    //私有, 静态变量 , 记录Singleton的唯一实例
    private static Singleton uniqueInstance;
    //私有的构造方法,只有在Singleton类内才可以调用构造器
    private Singleton(){}
    
    //静态方法,在此方法中实例化对象
    public static Singleton getInstance()
    {
        //延迟实例化,如果我们不需要这个实例,就永远不会创建,保证资源不被浪费
        if(uniqueInstance == null)
        {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

注意:单例模式的核心就在于拥有一个私有的构造方法,任何人想要创建实例,都不能直接创建,而是必须向 Singleton 做出请求,在 Singleton 类中进行判断,没有实例才进行创建,有的话直接返回。

 

现在我们可以尝试定义单例模式:确保一个类只有一个实例,并且提供一个全局访问点。

但是到这里,请注意,上述所有的都是针对于单线程来操作的,对于多线程来讲,可能会创建出多个实例。但是我们必须潜意识中认为所有程序都是多线程的,怎样保证多线程安全问题呢?有3种方式,各有优缺点,我们接下来进行分析:

方法一:将 getInstance() 方法变为同步 (synchronized) 的。

public class Singleton {
    
    private static Singleton uniqueInstance;
    
    private Singleton(){}
    
    //加入 synchronized 关键字
    public static synchronized Singleton getInstance()
    {    
        if(uniqueInstance == null)
        {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}

加入 synchronized 关键字之后,迫使每个线程在进入 getInstance() 方法的时候,没有线程在此方法中,也就是说,不会有两个线程同时进入该方法,从而保证线程安全。

但是此种处理方法的缺点在于,同步会导致很严重的性能问题,导致性能下降100倍。并且,只有在第一次执行此方法时,才需要同步(创建对象),以后执行的时候已经创建过对象,不需要同步。只是单纯的同步,会造成累赘。

 

方法二:放弃延迟化创建对象

public class Singleton {
    //放弃延迟化创建对象,在JVM加载此类的时候直接创建
    private static Singleton uniqueInstance = new Singleton();
    
    private Singleton(){}
    
    public static Singleton getInstance()
    {    
        return uniqueInstance;
    }
}

这样做保证了线程安全,但是又无法做到延迟化创建对象,如果在整个程序中一直用不到此对象,则会造成资源的浪费,和全局变量的效果类似。

 

方法三:利用双重检查加锁

public class Singleton {
    
    //加入 volatile 关键字
    private volatile static Singleton uniqueInstance;
    
    private Singleton(){}
    
    public static Singleton getInstance()
    {    
        //在这里进行双重判断,只有第一次执行的时候才创建实例,且保证线程安全
        if(uniqueInstance == null)
        {
            synchronized(Singleton.class)
            {
                if(uniqueInstance == null)
                {
                    uniqueInstance = new Singleton();
                }
            }    
        }
        return uniqueInstance;
    }
}

第三种方法可以避免性能上的影响,并且可以保证线程安全,但是此种方法不能适用于1.4以及更早的java版本。

 

综合以上分析,单例模式是由类管理自己的实例的一种典型的体现,单例类不仅负责管理自己的实例,还在应用程序中担当角色,可以看成是承担了两种责任,看起来像违背了"一个类,一个责任“的原则,但是在 java 中,这种方式很常见,也很特殊。并且,其实适合用单例模式的机会并不是很多。

单例模式的核心在于,私有构造方法,静态方法和静态变量。

就这些,祝好运。

 

posted on 2018-01-18 19:17  knot  阅读(122)  评论(0编辑  收藏  举报