java设计模式之单例模式
一、单例模式介绍
单例模式定义如下: 单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
单例模式的三个要点:
(1)某个类只能有一个实例;
(2)它必须自行创建这个实例;
(3)它必须自行向整个系统提供这个实例。
二、单例模式角色
Singleton(单例) :在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
三、单例模式的优缺点
主要优点:
(1)单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
(2)由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
(3)允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例单例对象共享过多有损性能的问题。
主要缺点:
(1)由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
(2)单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
(3)现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
四、单例模式的适用场景
(1)系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。
(2)客户调用类的单个实例只允许使用一个公共访问点,除了该公共访问点,不能通过其他途径访问该实例。
五、单例模式的结构图
六、单例模式的七种写法
1、饿汉式
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
这种方式和名字很贴切,饥不择食,在类装载的时候就创建,不管你用不用,先创建了再说,如果一直没有被使用,便浪费了空间,典型的空间换时间,每次调用的时候,就不需要再判断,节省了运行时间。
2、饿汉式变种
public class VarietasSingleton {
private static VarietasSingleton instance;
static {
instance = new VarietasSingleton();
}
private VarietasSingleton(){}
public static VarietasSingleton getInstance(){
return instance;
}
}
其会在类加载的时候就进行加载。和上面的饿汉式的写法优缺点相同。
3、懒汉式(非线程安全)
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
非线程安全的懒汉模式申明了一个静态对象,在用户第一次调用时初始化,虽然节约了资源,但第一次加载时需要实例化,反映稍慢一些,而且在多线程不能正常工作。在多线程访问的时候,很可能会造成多次实例化,就不再是单例了。
4、懒汉式(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
该懒汉式单例类在getInstance()方法前面增加了关键字synchronized进行线程锁,以处理多个线程同时访问的问题。但是,上述代码虽然解决了线程安全问题,但是每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低。如何既解决线程安全问题又不影响系统性能呢?我们继续对懒汉式单例进行改进。
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
5、双重校验锁(DCL)
public class Singleton {
//被volatile修饰的变量的值,将不会被本地线程缓存,
// 所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
//第一重判断
if (instance == null){
//锁定代码块
synchronized (Singleton.class){
//第二重判断
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(instance == null)判断,这种方式称为双重检查锁定(Double-CheckLocking)。
6、静态内部类
public class Singleton {
private Singleton(){}
private static class HolderClass{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return HolderClass.instance;
}
}
由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响。所以推荐使用静态内部类单例模式。
7、枚举
public enum Singleton {
INSTANCE;
public void doSomeThing(){
//do something...
}
//使用方法
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
instance.doSomeThing();
}
}
枚举单例的优点就是简单,但是大部分应用开发很少用枚举,可读性并不是很高,不建议使用。