设计模式之单例模式

所谓单例模式:保证一个类仅有一个实例 并提供一个访问它的全局访问点。
简单的说:单例设计模式就是一个类有且仅有一个对象。

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的方法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。

单例模式分为:1.饿汉式 2.懒汉式

饿汉式:在类加载的时候,直接将唯一的对象创建出来。
懒汉式:在类加载的时候,并不直接创建这个唯一的对象。而是在调用方法时,去判断是否创建过对象。这里分为两种情况:1.如果是已经创建过对象,那么直接返回。2.如果并没有创建一个对象,则创建一个对象 将其地址值返回。
饿汉式:
 1.将构造方法私有化,使其不能在类的外部通过new关键字来创建出该类对象。
 2.在类的内部产生一个唯一的实例化对象,并且将其封装成private static类型。
 3.通过一个静态方法返回这个唯一对象
饿汉式代码如下:
  public class Singleton {

    // 将自身实例化对象设置为一个属性,并用static、final修饰
    private static final Singleton instance = new Singleton();

    // 构造方法私有化
    private Singleton() {}

    // 静态方法返回该实例
    public static Singleton getInstance() {
             return instance;
}
}

饿汉式的优点:实现起来简单,没有多线程同步问题。
饿汉式的缺点:提前占用系统资源。(不管你用不用这个实例化对象,随着类的加载他已经被创建出来了。因此会占用内存)当类被卸载时,静态变量被摧毁,并释放所占用的内存,因而在某些特定情况下会耗费内存。

懒汉式:

懒汉式是延迟加载,在调用get()方法时,实例才被创建。(先不着急实例化对象,等要用的时候才给你创建出来。不着急,所以称为"懒汉式"),常见的实现懒汉式方法就是在get()方法中new一个实例化对象。

懒汉式代码如下:
public class Singleton {

// 将自身实例化对象设置为一个属性,并用static修饰
private static Singleton instance;

// 构造方法私有化
private Singleton() {}

// 静态方法返回该实例
public static Singleton getInstance() {
    if(instance == null) {
        instance = new Singleton();
    }
    return instance;
}
}

在这里 我们会先进行一个判断 如果对象不为null,那么直接返回。如果对象为null,那么使用new关键字创建出这个对象。

懒汉式优点:在第一次调用时才创建对象,节约内存。
懒汉式缺点:在多线程的环境下,这种实现方法根本无法保证单例的状态。

线程安全的懒汉式代码如下:

public class Singleton {

// 将自身实例化对象设置为一个属性,并用static修饰
private static Singleton instance;

// 构造方法私有化
private Singleton() {}

// 静态方法返回该实例,加synchronized关键字实现同步
public static synchronized Singleton getInstance() {
    if(instance == null) {
        instance = new Singleton();
    }
    return instance;
}
}

优点:在多线程的环境下,保证了”懒汉式”的线程安全问题。
缺点:总所周知在多线程环境下,synchronized方法通常效率低。因此这并不是最佳的实现方案。

DCL双检查锁机制(DCL:double checked locking)
 public class Singleton {
 // 将自身实例化对象设置为一个属性,并用static修饰
  private static Singleton instance;
 // 构造方法私有化
  private Singleton() {}

 // 静态方法返回该实例
public static Singleton getInstance() {
    // 第一次检查instance是否被实例化出来,如果没有进入if块
    if(instance == null) {
        synchronized (Singleton.class) {
            // 某个线程取得了类锁,实例化对象前第二次检查instance是否已经被实例化出来,如果没有,才最终实例出对象
            if (instance == null) {
                instance = new Singleton();
            }
        }
    }
    return instance;
}
}

关于为什么要做两次判断的解释: 对于instance存在的情况,就直接返回,这里没有问题。但是当instance为null并且有两个线程同时调用getInstance()方法时,这里它们都能通过第一次 instance == null 判断。然后由于同步代码块 synchronized,这两个线程只能有一个进入,另一个在外排队等待,必须要等第一个进去并出来以后,第二个才能进入。这里如果没有了第二次 instance==null的判断,则第一个线程进入创建了实例,在它出来之后,第二个线程依然可以进入创建实例。这样就没有达到单例的目的。

经过分析,貌似上面缩写的DCL代码是没有问题了。但是,如果我们深入分析,那么还是可能存在一定的问题。
如果我们深入到JVM的层面去探讨DCL这段代码,那么就有可能(仅仅是有可能)会产生问题。
这里从JVM创建对象所经历的步骤开始分析。因为虚拟机在执行创建实例的这一步操作的时候,其实是分了好几步去进行的,也就是说创建一个新的对象并非是原子性操作。在有些JVM中上述做法是没有问题的,但是有些情况下是会造成莫名的错误。
JVM创建对象需要经过三步:
1.分配内存
2.初始化构造器
3.将对象指向分配的内存的地址
这种顺序针对上述DCL代码中是完全可以执行的,因为JVM执行了整个流程创建了对象才将内存地址交给了对象。但是,如果2.3调换了顺序那么会有问题吗?答案是肯定的!(2.3可能调换顺序的原因是JVM会针对字节码调优,其中有一项就是调整指令的执行顺序)

如果2.3调换了顺序,那么内存地址首先就会交给对象。针对上面的DCL代码,内存地址会先交给instance 对象,然后再进行初始化构造器。这时,后面的线程去请求getInstance()方法时,会认为instance对象已经是被创建了,然后就会返回一个引用。但是如果在初始化构造器之前,这个线程去使用了instance对象,就会产生莫名的错误。

推荐方案: 静态内部类实现单例模式
静态内部类代码如下:
public class SingLeton {
private static class Inner{
    private static final SingLeton instance=new SingLeton();
}

private SingLeton(){

}
private static final SingLeton getInstance(){
    return Inner.instance;
}
}

这种静态内部类实现单例模式是可以完全避免上述错误的!一个类的静态属性只会在第一次加载类时初始化。同时,JVM保证在类加载的过程中static代码块在多线程或者单线程下都正确执行,且仅执行一次。在初始化进行一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。另外由于静态变量只初始化一次,所以Singleton仍然是单例的。解决了延迟加载以及线程安全的问题。

关于单例模式的分享到此结束,感谢各位观看,欢迎交流,谢谢!

最后 学友哥镇楼:
在这里插入图片学友哥描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值