设计模式之单例模式

一、单例模式思想

单例模式的主要思想是:

  • 将构造方法私有化( 声明为 private ),这样外界不能随意 new 出新的实例对象;
  • 声明一个私有的静态的实例对象,供外界使用;
  • 提供一个公开的方法,让外界获得该类的实例对象。

这种说法看上去没错,但也好像不太准确。其实,就算外界能随意 new 出新的实例对象,但只要我们保证我们每次使用的对象是唯一的,就可以。


二、单例模式实现方式

1. 饿汉式(线程安全,可用)

public class Singleton {
    private Singleton() {
    }

    private static Singleton sSingleton = new Singleton();

    public static Singleton getInstance() {
        return sSingleton;
    }
}
  • 优点:JVM在加载这个类时马上创建此类唯一的单例实例,在任何线程访问sSingleton 静态变量之前,保证了线程安全;
  • 缺点: 类一加载的时候,就实例化,提前占用了系统资源。如果程序在运行过程中没有用到该单例静态变量,势必会造成资源浪费。

2. 懒汉式(线程不安全,并发场景不可用)

public class Singleton {
    private Singleton() {
    }

    private static Singleton sSingleton;

    public static Singleton getInstance() {
        if (sSingleton == null) {
            sSingleton = new Singleton();
        }
        return sSingleton;
    }
}
  • 优点:延迟实例化,即用到时才会实例化;
  • 缺点:第一次加载时反应稍慢,线程不安全,无法用于并发场景。

3. 同步的懒汉式(线程安全,可用,不建议使用)

public class Singleton {
    private Singleton() {
    }

    private static Singleton sSingleton;

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

缺点:性能比较低,每次调用 getInstance( ) 都需要进行同步(去抢锁),实际上我们仅需要第一次执行该方法时进行同步,造成了不必要的同步开销,同步一个方法可能会造成程序执行效率下降100倍,这种模式一般不建议使用。


4. 双重检查锁 (线程安全,大多数场景满足需求,推荐使用)

public class Singleton {
    private Singleton() {
    }

    /**
     * volatile is since JDK5
     */
    private static volatile Singleton sSingleton;

    public static Singleton getInstance() {
        if (sSingleton == null) {
            synchronized (Singleton.class) {
                // 未初始化,则初始instance变量
                if (sSingleton == null) {
                    sSingleton = new Singleton();
                }
            }
        }
        return sSingleton;
    }
}
  • 优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,并且线程安全;
  • 缺点:由于Java内存模型的原因偶尔会失败,在高并发环境下也有一定的缺陷,虽然发生的概率很小。

双重检查锁进行了两次判断,第一次是为了判断是否已经被实例化,第二次是为了进行同步,避免多线程问题。由于 sSingleton = new Singleton()不是一个原子操作,对象的创建在JVM中可能会进行指令重排,在多线程访问下存在风险,使用volatile修饰解决该问题。双重检查锁是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于jdk1.6版本下使用,否则这种方式一般能够满足需求。


5. 静态内部类(线程安全,推荐使用)

public class Singleton {

    private Singleton () {
    }

    private static class InnerClassSingleton {
     private final static Singleton sSingleton = new Singleton();
    }

    public static Singleton getInstance() {
        return InnerClassSingleton.sSingleton;
    }
}

只有第一次调用getInstance方法时,虚拟机才加载 Inner 并初始化instance ,只有一个线程可以获得对象的初始化锁(因为JVM在初始化内部类时,会保证只有一个线程去初始化这个内部类),其他线程无法进行初始化,保证对象的唯一性。目前此方式是所有单例模式中最推荐的模式,但具体还是根据项目选择。


6. 枚举单例(线程安全)

public enum Singleton{
    INSTANCE;
    
    // 其它方法
    public void doSomething(){
        ...
    }
}
  • 优点:枚举实现单例很简单,也很安全,并且可以防止反射破坏单例;
  • 缺点:可读性较差,且相比于静态常量Enum会花费两倍以上的内存。

枚举单例模式是在《Effective Java》中推荐的单例模式之一,但枚举实例在日常开发是很少使用的,就是很简单以导致可读性较差。在以上所有的单例模式中,推荐静态内部类单例模式。主要是非常直观,即保证线程安全又保证唯一性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值