单例模式

Posted terry2410

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了单例模式相关的知识,希望对你有一定的参考价值。

1.前言

单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实力。这个类成为单例类。

 

2.单例模式的结构

单例模式的三个特点:

1.单例类只能有一个实例

2.单例类必须自己创建自己的唯一实例

3.单例类必须给所有其他对象提供这一实例

 

饿汉式单例类

饿汉式单例类是Java语言里实现起来最为简便的单例类

public class EagerSingleton {

    private static final EagerSingleton m_instance 
            = new EagerSingleton();
    
    //私有构造函数
    private EagerSingleton(){}
    
    //静态工厂方法
    public static EagerSingleton getInstance(){
        
        return m_instance;
    }
}

 

这个类被加载时,静态变量 m_instance 会被初始化,此时类的私有构造函数(构造子)会被调用,单例类的唯一实例也就被创建出来了。

单例类的构造函数是私有的,从而避免外界利用构造函数直接创建出任意多的实例。值得指出的是类,由于构造函数是私有的,因此此类不能被继承。

 

懒汉式单例类

与饿汉式单例类相同之处是:类的构造函数都是私有的。

不同之处是:懒汉式单例类在第一次被引用时将自己实例化。

懒汉式单例类被加载时不会将自己实例化。

public class LazySingleton {

    private static LazySingleton m_instance = null;
    
    //私有的默认构造函数,保证外界无法直接实例化
    private LazySingleton(){}
    
    //静态工厂方法,返还此类的唯一实例
    synchronized public static  LazySingleton getInstance(){
        
        if(m_instance == null){
            m_instance = new LazySingleton();
        }
        return m_instance;
    }
}

 

上面懒汉式单例类实现里对静态工程方法使用了同步化,以处理多线程环境。同样,由于构造函数式私有的,此类也不能被继承。

“双重检查成例”不可以在Java语言中使用。

 

本书认为饿汉式单例类更符合Java语言本身的特点。

 

登记式单例类

登记式单例类是为了克服饿汉式单例类及懒汉式单例类均不可继承的缺点而是设计的。

登记式单例父类

//登记式单例父类
public class RegSingleton {

    private static HashMap m_registry = new HashMap();
    static {
        RegSingleton x = new RegSingleton();
        m_registry.put(x.getClass(), x);
    }

    //保护的默认构造函数
    protected RegSingleton(){}

    //静态工厂方法,返还此类唯一的实例
    public static RegSingleton getInstance(String name ){

        if(name == null){
            name = "com.sankuai.singleton.RegSingleton";
        }
        if(m_registry.get(name) == null){
            try {
                m_registry.put(name, Class.forName(name).newInstance());
            }catch (Exception e){
                System.out.println("error happened.");
            }
        }

        return (RegSingleton)(m_registry.get(name));
    }

    public String about(){
        return "Hello, I am RegSinleton";
    }

}

登记式单例子类

//登记式单例子类
public class RegSingletonChild {

    public RegSingletonChild(){}

    //静态工厂方法
    public static RegSingletonChild getInstance(){

        return (RegSingletonChild) RegSingleton.getInstance("com.sankuai.singleton.RegSingletonChild");
    }

    public String about(){
        return "Hello, I am RegSingletonChild."
    }
}

 

由于子类必须允许父类以构造子调用产生实例,因为,它的构造函数必须是公开的。这样,就等于允许了以这样的方式产生实例而不在父类的登记中。这是登记式单例类的一个缺点。

 

 

3.在什么情况下使用单例模式

使用单例模式有一个必要条件:在一个系统要求一个类只有一个实例时才应当使用单例模式。

 

4.单例类的状态

有状态的单例类

一个单例类可以是有状态的(stateful).

有状态的可变的单例对象常常当做状态库(repositary)使用。比如一个单例对象可以持有一个int类型的属性,用来给一个系统提供一个数值唯一的序列号,作为某个贩卖系统的账单号码。

 

没有状态的单例类

单例类也可以是没有状态的,仅用做提供工具性函数的对象。

 

5.Java语言中的单例模式

Java的Runtime对象

在Java语言内部,java.lang.Runtime对象就是一个使用单例模式的例子。在每一个Java应用程序里面,都有唯一的一个Runtime对象。通过这个Runtime对象,应用程序可以与其运行环境发生相互作用。

Runtime对象通常的用途包括:执行外部命令;返回现有内存即全部内存;运行垃圾收集器;加载动态库等。

java.awt.Toolkit类

Toolkit类提供了一个静态的方法 getDefaultTookit()来提供这个唯一的实例,这个方法相当于懒汉式的单例方法,因此整个方法都是同步化的。

由于ToolKit 是一个抽象类,如果其子类提供私有构造函数,那么子类是一个正常的单例类; 如果子类提供一个公开的构造函数,这个子类便是“不完全”的单例类。

 

6.不完全的单例类

这样看起来是一个“懒汉”式的单例类,却有一个公开的构造函数。由于外界可以创建出多个此类的实例,因为称为“不完全的单例类”。

//不完全的单例类
public class LazySingleton {

    private static LazySingleton m_instance = null;
    
    //公开构造函数,外界可以直接实例化
    public LazySingleton(){}
    
    //静态工厂方法,返还此类的唯一实例
    synchronized public static  LazySingleton getInstance(){
        
        if(m_instance == null){
            m_instance = new LazySingleton();
        }
        return m_instance;
    }
}

 

造成这种情况的原因:

1.初学者的错误

2.当初考虑不周,设计成单例例,后因需要多个实例,干脆将构造函数改为公开的

3.设计师有意为之,意在使用一种“改良”的单例模式。

 

7.双重检查成例对Java语言编译器不成立

public class LazySingleton {

    private static LazySingleton m_instance = null;

    //私有的默认构造函数,保证外界无法直接实例化
    private LazySingleton(){}

    //静态工厂方法,返还此类的唯一实例
    public static  LazySingleton getInstance(){

        if(m_instance == null){  //第一次检查(位置1)
						
           //这里会有多于一个的线程同时到达(位置2)
            synchronized (LazySingleton.class){
              
              	//这里在每个时刻只能有一个线程(位置3)
                if(m_instance == null){ //第二次检查(位置4)
                    m_instance = new LazySingleton();
                }
            }
        }
        return m_instance;
    }
}

 

一般而言,双重检查成立对Java语言来说是不成立的。在Java编译器中,LazySingleton类的初始化与 m_instance变量赋值的顺序不可预料。如果一个线程在没有同步话的条件下读取m_instance引用,并调用这个对象的方法的话,可能会发现对象的初始化过程尚未完成,从而造成崩溃。

 

但是,但是:

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

public class LazySingleton {

    private  volatile static LazySingleton m_instance = null;

    //私有的默认构造函数,保证外界无法直接实例化
    private LazySingleton(){}

    //静态工厂方法,返还此类的唯一实例
    public static  LazySingleton getInstance(){

        if(m_instance == null){  //第一次检查(位置1)
						
           //这里会有多于一个的线程同时到达(位置2)
            synchronized (LazySingleton.class){
              
              	//这里在每个时刻只能有一个线程(位置3)
                if(m_instance == null){ //第二次检查(位置4)
                    m_instance = new LazySingleton();
                }
            }
        }
        return m_instance;
    }
}

注意:在1.4及更早版本的Java中,许多JVM对于volatile关键字的实现会导致双重检查加锁的失效。

 

 

 

8.一点建议

在一般情况下,使用饿汉式单例模式或者对整个静态工厂方法同步化的懒汉式单例模式足以解决在实际设计工作中遇到的问题。

以上是关于单例模式的主要内容,如果未能解决你的问题,请参考以下文章

常用代码片段

性能比较好的单例写法

片段作为 Android 中的单例

单例片段或保存网页视图状态

你熟悉的设计模式都有哪些?写出单例模式的实现代码

单例模式以及静态代码块