单例模式详解

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。在多线程更是要注意。

参考:

  1. JAVA设计模式之单例模式
  2. 单例模式与双重检测
  3. The “Double-Checked Locking is Broken” Declaration
  4. Java多线程编程环境中单例模式的实现

单例模式的特点:

  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

懒汉式单例

/** * 懒汉式,在第一次调用的时候实例化自己<p> * 本身是线程不安全的,并发环境下很可能出现多个Singleton实例。 * 如果在静态工厂上加了同步,虽然线程安全了,但是每次都要同步, * 会影响性能,毕竟99%的情况下是不需要同步的 */
public class LazySingleton{
    private static LazySingleton singleton = null;

    private LazySingleton(){
    }
    //静态工厂方法 。此方法设置成 静态同步方法就可线程安全 
// public static synchronized LazySingleton getInstance(){
    public static LazySingleton getInstance(){
        if(singleton == null){
            singleton = new LazySingleton();
        }
        return singleton;
    }
/* //另外一种方法(双重检查锁定) public static LazySingleton getInstance(){ if(singleton == null){ synchronized (LazySingleton.class) { if(singleton == null){ singleton = new LazySingleton(); } } } return singleton; } */
}

Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。

懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例。
要实现线程安全可以对getInstance这个方法改造,保证了懒汉式单例的线程安全。

1.将getInstance()方法设置成 静态同步方法就可线程安全。

    public static synchronized LazySingleton getInstance(){
        if(singleton == null){
            singleton = new LazySingleton();
        }
        return singleton;
    }

这种办法很影响性能:每次调用getInstance方法的时候都必须获得Singleton的锁,而实际上,当单例实例被创建以后,其后的请求没有必要再使用互斥机制了

2.将getInstance()方法内部添加双重检查锁定

    public static LazySingleton getInstance(){
        if(singleton == null){
            //注意这里不能写 synchronized(this) 因为这是静态方法内部,this从哪里来
            synchronized (LazySingleton.class) {
                if(singleton == null){
                    singleton = new LazySingleton();
                }
            }
        }
        return singleton;
    }

*有出错成的可能,以A、B两个线程为例:

  1. A、B线程同时进入了第一个if判断
  2. A首先进入synchronized块,由于instance为null,所以它执行instance = new Singleton();
  3. 由于JVM内部的优化机制,JVM先画出了一些分配给Singleton实例的空白内存,并赋值给instance成员(注意此时JVM没有开始初始化这个实例),然后A离开了synchronized块。
  4. B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
  5. 此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。
    *

饿汉式单例

/** * 饿汉式<p> * 饿汉式单例类.在类初始化时,已经自行实例化 * 饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。 */
public class HungrySingleton {
    private static HungrySingleton singleton = new HungrySingleton();

    private HungrySingleton(){
    }

    public static HungrySingleton getInstance(){
        return singleton;
    }
}

这样的代码缺点是:第一次加载类的时候会连带着创建Singleton实例,这样的结果与我们所期望的不同,因为创建实例的时候可能并不是我们需要这个实例的时候。同时如果这个Singleton实例的创建非常消耗系统资源,而应用始终都没有使用Singleton实例,那么创建Singleton消耗的系统资源就被白白浪费了。

静态内部类式

/** * 静态内部类<p> * 相比懒汉式,实现了线程安全,又避免了同步带来的性能影响 */
public class InternalSingleton {
    private InternalSingleton(){}

    public static InternalSingleton getInstance(){
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder{
        private final static InternalSingleton INSTANCE = new InternalSingleton();
    }
}

JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕。此外该方法也只会在第一次调用的时候使用互斥机制,这样就解决了”静态同步方法”的问题。最后instance是在第一次加载SingletonContainer类时被创建的,而SingletonContainer类则在调用getInstance方法的时候才会被加载,因此也实现了惰性加载。

测试:

import org.junit.Test;

public class GetSingletonInstance {
    @Test
    public void getLazySingletonInstance() {
        LazySingleton singleton1 = LazySingleton.getInstance();
        LazySingleton singleton2 = LazySingleton.getInstance();
        if(singleton1 == singleton2){
            System.out.println("是同一个实例!");
        }else{
            System.out.println("不是同一个实例!");
        }
    }

    @Test
    public void getHungrySingletonInstance() {
        HungrySingleton singleton1 = HungrySingleton.getInstance();
        HungrySingleton singleton2 = HungrySingleton.getInstance();
        if(singleton1 == singleton2){
            System.out.println("是同一个实例!");
        }else{
            System.out.println("不是同一个实例!");
        }
    }

    @Test
    public void getInternalSingletonInstance() {
        InternalSingleton singleton1 = InternalSingleton.getInstance();
        InternalSingleton singleton2 = InternalSingleton.getInstance();
        if(singleton1 == singleton2){
            System.out.println("是同一个实例!");
        }else{
            System.out.println("不是同一个实例!");
        }
    }
}

结果:

是同一个实例!
是同一个实例!
是同一个实例!

结论

优先使用 静态内部类式

你可能感兴趣的:(单例模式,Singleton)