浅析设计模式-单例模式

2017-08-21  本文已影响112人  RunAlgorithm

定义

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

现实世界模型:

比如古代一个国家只能有一个皇帝

单例模式需要满足以下特点:

简单设计

单例模式的类图可以简单表示为:

单例模式-类图.png

要正确的写好单例,需要关注两点:

主要分两种方法,懒汉和饿汉,区别在初始化唯一实例的时机。懒汉延迟初始化,饿汉在类加载的时候就初始化了

同时 Java 还有一个特殊的实现方式,使用枚举实现

懒汉

延迟加载单例,只有第一次使用的时候才进行实例化

懒汉需要做一些线程安全的处理

如果没有进行多线程处理,比如:

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

假设有两个线程,A 线程和 B 线程,两个线程同时调用 getInstance

当 A 线程执行 instance = new Singleton(); 语句还没结束时,instance 还为 null

这时候 B 线程进入语句 if (instance == null) ,条件成立,也进入 instance = new Singleton();

这时候,这个类就会产生两个实例,都被外界拿去使用了

保证延迟加载并且线程安全的做法主要有这三种:

方法加同步

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

直接在 getInstance() 方法加同步,线程安全

缺点是调用 getInstance 比较密集的场景,同步方法频繁调用,性能低

静态内部类

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

利用 Java 的类加载机制,来延迟初始化对象实例。类被主动调用的时候,如果没有加载会执行加载

我们这里使用的是静态内部类,在没有被访问前,还没有进行加载。在使用 getInstance 方法后,该静态内部类被主动调用,于是开始了对这个类的类加载过程。

因为 Java 的类加载过程是同步的,包括类静态成员的初始化也是同步的,这个做法无需再单独加锁

ClassLoader 加载类的代码:

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            ...
            return c;
        }
    }

可见有对加载进行同步处理

双重检查锁定

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getInstance() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
} 

双重检查锁定和同步静态方法对比,把锁的粒度降低,所以只有在判断当前实例为空,才会进入被锁住的代码。这样子提高了使用的性能,保证在实例存在的情况下,不会因为互斥锁导致多个线程阻塞等待的现象

同时 volatile 关键字还确保了,虚拟机进行指令优化的时候,不会进行重排序导致对象延迟初始化

volatile 关键字的主要功能有三:

如果没有加 volatile 关键字,由于虚拟机的指令排序优化,会把对象创建延迟到使用的时候。会出现 singleton 返回不会 null 的时候,但是还没有执行初始化对象实例,当另一个线程拿到对象实例,即使不为 null,但实际还是未初始化的对象

饿汉

在类加载后就实例化出一个静态对象出来,

静态工厂方法

public class Singleton {  
    private Singleton() {}  
    private static final Singleton singleton = new Singleton();  
    public static Singleton getInstance() {  
        return singleton;  
    }  
}  

饿汉始终是线程安全的

他的缺点后面即使没有使用单例,也会一直存在,某些场景下会耗费内存(比如某个进程一直都没有使用到那个单例,而这个单例持有大量内存,造成不必要的浪费)

枚举

public enum Singleton {
    INSTANCE;
    private Singleton() {
    }
}

创建枚举默认是线程安全的,同时不需要担心反序列化导致重新创建新对象

应用

单例模式应用范围很广,可以在这里见到

控制实例的访问,需要访问一定在同一实例上进行的,比如 HttpClient

控制资源的使用,和线程池或者对象池配合使用,资源类型的单例,避免创建多个池浪费资源,节约内存

控制对象的创建,不需要多次重复创建,和工厂模式配合使用,一些工厂只需要进程中只保持一份,

优缺点

优势:

缺点:

使用注意

单例模式,实例一旦初始化,就会在内存中一直存在,要注意几点

上一篇下一篇

猜你喜欢

热点阅读