设计模式 单例模式

定义

确保某一个类只有一个实例,而且自行实例化并且向整个系统提供这个实例,在实际应用中,线程池,日志,缓存对象,对话框,都会被设计成单例,选择单例模式就是为了避免不一致的情况,

使用场景

确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且一个,例如,创建一个对象需要消耗资源过多,如访问IO和数据库等资源,这时就要考虑单例模式

1 饿汉式单例模式

/**
 * 饿汉式(基于classloder机制避免了多线程的同步问题)
 */
public class SingletonHungry {

    private static SingletonHungry instance = new SingletonHungry();

    private SingletonHungry() {
    }

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

从上述代码中看出,类不能通过new的方式构造对象,只能通过getInstance来获取,这个sDanLiDemo 是静态的并且他是随类的加载而初始化的,这就保证了对象的唯一性,这个实现的核心在于构造方法的私有化,使得外部不能通过构造函数来构造对象,而通过一个静态方法返回一个对象

这种方法虽然简单,但是他不能做到延迟加载,事实上,如果该单例类涉及资源较多,创建比较耗费时间,我们希望他能够延迟加载,从而减少初始化的负载,于是就有了以下的懒汉式

sDanLiDemo 是随着类的加载而初始化的,那么类什么时候加载?

类什么时候加载

类的加载是通过类加载器(Classloader)完成的,它既可以是饿汉式[eagerly load](只要有其它类引用了它就加载)加载类,也可以是懒加载[lazy load](等到类初始化发生的时候才加载)。不过我相信这跟不同的JVM实现有关,然而他又是受JLS保证的(当有静态初始化需求的时候才被加载)。

类什么时候初始化
加载完类后,类的初始化就会发生,意味着它会初始化所有类静态成员,以下情况一个类被初始化:

1实例通过使用new()关键字创建或者使用class.forName()反射,但它有可能导致ClassNotFoundException。
2类的静态方法被调用
3类的静态域被赋值
4静态域被访问,而且它不是常量
5在顶层类中执行assert语句

2 懒汉式单例模式

/**
 * 懒汉式单例模式(适合多线程安全)
 */
public class SingletonLazy {

    private static volatile SingletonLazy instance;

    private SingletonLazy() {
    }

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

懒汉式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化,在getInstance加了synchronized 关键字,也就是说这是一个同步方法,这就是多线程情况下保证单例模式的手段,这个也是有问题的,即使已经被初始化,每次调用getInstance还会进行同步,这样会消耗不必要的资源,这也是懒汉模式的最大问题,为了兼顾效率和安全,下面是进阶版

Double Check Lock (DCL)实现单例


public class DanLiDemo {
    private static DanLiDemo sDanLiDemo2 = null;

    private DanLiDemo() {
    }

 
    //DCL实现单例

    public static DanLiDemo gettInstance1() {
        if (sDanLiDemo2 == null) {
            synchronized (DanLiDemo.class) {
                if (sDanLiDemo2 == null) {
                    sDanLiDemo2 = new DanLiDemo();
                }
            }
        }
        return sDanLiDemo2;
    }

}

这里写图片描述

这里写图片描述

静态单例模式

DCL虽然在一定程度上解决了资源消耗,多余的同步,线程安全问题,但是,他还是某种情况下出现失效问题,这个问题被称为双重检查锁定(DCL失效)在《Java并发编程实践》一书谈到这个问题不赞成是使用,而建议如下下代码替代


public class DanLiDemo {

    private DanLiDemo() {
    }

    //静态内部单例模式

    public static DanLiDemo getInstance2() {
        return DanLiDemoViewHolder.danli;
    }

    private static class DanLiDemoViewHolder {
        private static final DanLiDemo danli = new DanLiDemo();
    }

}

当第一次加载DanLiDemo时并不会初始化danli ,只有第一次调用DanLiDemo的getInstance方法才会导致danli的初始化,第一次调用getInstance方法会导致虚拟机加载DanLiDemoViewHolder类,这种方式不仅能够确保线程安全,也能办理对象的唯一性,同时也延迟了单例的实例化,所以推荐使用这种单例模式

这里写图片描述

但是上方的写法都有共同的缺点
1 序列化可能会破坏单例,比较每次反序列化每个序列化对象,都会创建一个新的实例对象,解决方案如下

//测试例子(四种写解决方式雷同)
public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     

   protected Singleton() {     
   }  

   //反序列时直接返回当前INSTANCE
   private Object readResolve() {     
            return INSTANCE;     
      }    
}  

2 使用反射强行调用私有构造器,解决方案,第二次创建对象时,抛出异常

public static Singleton INSTANCE = new Singleton();     
private static volatile  boolean  flag = true;
private Singleton(){
    if(flag){
    flag = false;   
    }else{
        throw new RuntimeException("The instance  already exists !");
    }
}

关于单例,我们总是应该记住:线程安全,延迟加载,序列化与反序列化安全,反射安全是很重重要的

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值