"1. 单例模式简介 单例是一种常见的设计模式,其特点为: 只能有一个实例。 必须自己自己创建自己的唯一实例。 必须给所有其他对象提供这一实例。 那单例的使用场景是什么呢?使用单例有什么好处呢? 对于系统中的某些类来说,只有一个实例很 重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作 ...."

单例模式

1. 单例模式简介

单例是一种常见的设计模式,其特点为:

  • 只能有一个实例。
  • 必须自己自己创建自己的唯一实例。
  • 必须给所有其他对象提供这一实例。

那单例的使用场景是什么呢?使用单例有什么好处呢?
对于系统中的某些类来说,只有一个实例很 重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或 ID(序号)生成器。如在 Windows 中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全 一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才 是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止 我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的使用动机。

2. 实现单例的几种方法

2.1. 懒汉式(线程不安全)

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  

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

因为 getInstance 方法没有做线程同步处理,如果有多个线程同时调用了 getInstance 方法的话,则有可能会出现 Singleton 实例化了多次的现象。
所以说这种写法是线程不安全的。

2.2. 懒汉式(线程安全)

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

这种写法在 getInstance 方法中使用了 synchronized 关键字 ,保证该方法是线程同步的。但随之而来的效率的问题,因为涉及到多线程的同步,效率总是比较低的,而且除了在第一次实例化时需要同步之外,其他情况下其实我们并不需要同步的。所以这种写法也不太合理。

2.3. 双重判断 + 同步锁

public class Singleton {

    private static volatile Singleton instance = null;

    // private constructor suppresses
    private Singleton(){
    }

    public static Singleton getInstance() {
        // if already inited, no need to get lock everytime
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }

        return instance;
    }
}

getInstance 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。可以看到上面在 synchronized (Singleton.class) 外又添加了一层 if,这是为了在 instance 已经实例化后下次进入不必执行 synchronized (Singleton.class) 获取对象锁,从而提高性能。那为什么 synchronized (Singleton.class) 里面不直接 instance = new Singleton(),还要再判断一次呢?假设 instance 未实例化,这时有很多线程同时调用 getInstance()。只有一个线程获得了对象锁,进入了安全区域对 instance 进行了实例化。完成实例化后就释放了对象锁,这时之前等待的线程也会获得对象锁,进入安全区域,但此时 instance 已经被初始化了。所以为了避免重复初始化,还是有必要再判断一次是否有初始化的。

2.4. 饿汉式

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

    private Singleton() {}

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

这种方式基于 classloder 机制避免了多线程的同步问题,在类被加载时就已经实例化了。所以既是线程安全的,效率也高。问题是会提前占用系统资源,而且不能够传参数给构造函数。如果不考虑系统资源占用,且不需要传参数给构造函数,这是一种简单可靠的方式。

2.5. 单元素枚举类

public enum Singleton {  
    instance;  

    private String name;
    public String getName() {
        return name;
    }
}

使用枚举来实现单例的方法是从 <> 上看到的,也是作者推荐的方法。通过仅仅声明一个枚举量来表示这个单例的实例。枚举类也可以看作一个普通的类,也可以定义成员变量,成员方法。使用这种方法不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,还可以避免通过反射的方法创建多个实例。但是失去了类的一些特性,没有延迟加载,也不能传参数给构造函数。

2.6. 静态内部类

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

这种方式同样利用了 classloder 的机制来保证初始化 instance 时只有一个线程,它跟 2.4中介绍的饿汉式方式不同的是2.4中的方式是只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到lazy loading效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。这种方法比较取巧,涉及到静态内部类的加载时机。我想一般人不会这么干吧。

个人推荐使用双重判断+同步锁的方法,既能简单又能延迟加载,效率又高。

0     0     0     0     0    
0 回帖