天天看点

创建型-单例模式(singleton)

意图:保证一个类只有一个实例,并提供访问它的全局变量。

动机:例如一个会计系统只能专用于一个公司。为保证没有其他实例对象可以被创建,并且可以提供一个访问该实例的方法。

适用性:当类只能被一个实例而且客户可以从一个众所周知的访问点访问它时。

当这个唯一实例应该是通过子类化可扩展,并且客户应该无须更改代码就能使用一个拓展的实例时。

单例模式结构图:

创建型-单例模式(singleton)

协作:用户只能通过getInstance操作访问实例对象。

单例模式有三个要点:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。

Singleton(单例):在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。

单例模式的创建方法:

饿汉式:线程安全,直接创建对象,造成浪费。

class EagerSingleton {     
    private static final EagerSingleton instance = new EagerSingleton();     
    private EagerSingleton() { }     

    public static EagerSingleton getInstance() {    
        return instance;     
    }       
}
           

懒汉式:懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)技术,即需要的时候再加载实例,多个线程同时调用getInstance()方法会出现线程不安全问题。

class LazySingleton {     
    private static LazySingleton instance = null;     

    private LazySingleton() { }     

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

懒汉式加锁:该懒汉式单例类在getInstance()方法前面增加了关键字synchronized进行线程锁,以处理多个线程同时访问的问题。但是,上述代码虽然解决了线程安全问题,但是每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低。

class LazySingleton {     
    private static LazySingleton instance = null;     
    private LazySingleton() { }       
    synchronized public static LazySingleton getInstance() {     
        if (instance == null) {    
            instance = new LazySingleton();     
        }    
        return instance;     
    }    
} 
           

如何既解决线程安全问题又不影响系统性能呢?我们继续对懒汉式单例进行改进。事实上,我们无须对整个getInstance()方法进行锁定,只需对其中的代码“instance = new LazySingleton();”进行锁定即可。因此getInstance()方法可以进行如下改进:

class LazySingleton {     
    private static LazySingleton instance = null;     
    private LazySingleton() { }       
    public static LazySingleton getInstance() {     
    if (instance == null) {    
        synchronized (LazySingleton.class) {    
            instance = new LazySingleton();     
            }    
        }    
        return instance;     
    }    
} 
           

双重检查锁定:假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(instance == null)判断。

class LazySingleton {     
    private volatile static LazySingleton instance = null;         
    private LazySingleton() { }        
    public static LazySingleton getInstance() {     
        //第一重判断    
        if (instance == null) {    
            //锁定代码块    
            synchronized (LazySingleton.class) {    
                //第二重判断    
                if (instance == null) {    
                    instance = new LazySingleton(); //创建单例实例    
                }    
            }    
        }    
        return instance;     
    }    
}    
           

(静态内部类)由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。

public class Singleton {  
     private static class SingletonHolder {  
      private static final Singleton INSTANCE = new Singleton();  
     }  
     private Singleton (){}  
      public static final Singleton getInstance() {  
           return SingletonHolder.INSTANCE;  
      }  
  } 
           

枚举单例模式:

public class EnumSingleton{
    private EnumSingleton(){}
    public static EnumSingleton getInstance(){
        return Singleton.INSTANCE.getInstance();
    }  
    private static enum Singleton{
        INSTANCE; 
        private EnumSingleton singleton;
        //JVM会保证此方法绝对只调用一次
        private Singleton(){
            singleton = new EnumSingleton();
        }
        public EnumSingleton getInstance(){
            return singleton;
        }
    }
}
           

1.主要优点

单例模式的主要优点如下:

(1) 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。

(2) 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。

(3) 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。

2.主要缺点

单例模式的主要缺点如下:

(1) 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。

(2) 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。

(3) 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。

继续阅读