单列模式详细介绍!超级详细版本,看不懂你打我!

原文:https://zhuanlan.zhihu.com/p/51854665

什么是单例模式?

保证整个系统中一个类只有一个对象的实例,实现这种功能的方式就叫单例模式。

为什么要用单例模式?
1、单例模式节省公共资源

比如:大家都要喝水,但是没必要每人家里都打一口井是吧,通常的做法是整个村里打一个井就够了,大家都从这个井里面打水喝。

对应到我们计算机里面,像日志管理、打印机、数据库连接池、应用配置。

2、单例模式方便控制

就像日志管理,如果多个人同时来写日志,你一笔我一笔那整个日志文件都乱七八糟,如果想要控制日志的正确性,那么必须要对关键的代码进行上锁,只能一个一个按照顺序来写,而单例模式只有一个人来向日志里写入信息方便控制,避免了这种多人干扰的问题出现。

实现单例模式的思路

  1. 构造私有:

如果要保证一个类不能多次被实例化,那么我肯定要阻止对象被new 出来,所以需要把类的所有构造方法私有化。

2.以静态方法返回实例。

因为外界就不能通过new来获得对象,所以我们要通过提供类的方法来让外界获取对象实例。

3.确保对象实例只有一个。

只对类进行一次实例化,以后都直接获取第一次实例化的对象。

/**

  • 单例模式案例
    */
    public class Singleton {
    //确保对象实例只有一个。
    private static final Singleton singleton = new Singleton();
    //构造方法私有
    private Singleton() {
    }
    //以静态方法返回实例
    public static Singleton getInstance() {
    return singleton;
    }
    }
    这里类的实例在类初始化的时候已经生成,不再进行第二次实例化了,而外界只能通过SingleCase.getInstance()方法来获取SingleCase对象, 所以这样就保证整个系统只能获取一个类的对象实例。

几种单例模式的区别
饿汉模式

饿汉模式的意思是,我先把对象(面包)创建好,等我要用(吃)的直接直接来拿就行了。

public class Singleton {

//先把对象创建好
private static final Singleton singleton = new Singleton();

private Singleton() {
}

//其他人来拿的时候直接返回已创建好的对象

public static Singleton getInstance() {
return singleton;
}
}
我们上面的案例就是使用的饿汉模式。 这种模式是最简单最省心的,不足的地方是容易造成资源上的浪费(比如:我事先把面包都做好了,但是你并不一定吃,这样容易造成资源的浪费)。

懒汉模式

因为饿汉模式可能会造成资源浪费的问题,所以就有了懒汉模式,

懒汉模式的意思是,我先不创建类的对象实例,等你需要的时候我再创建。

/**

  • 单例模式案例
    */
    public class Singleton {

private static Singleton singleton = null;

private Singleton() {
}
//获取对象的时候再进行实例化
public static Singleton getInstance() {
synchronized (Singleton.class) {

if (singleton == null) {
singleton = new Singleton();
}

}
return singleton;
}
}

懒汉模式在并发情况下可能引起的问题

懒汉模式解决了饿汉模式可能引起的资源浪费问题,因为这种模式只有在用户要使用的时候才会实例化对象。但是这种模式在并发情况下会出现创建多个对象的情况。

因为可能出现外界多人同时访问SingleCase.getInstance()方法,这里可能会出现因为并发问题导致类被实例化多次,所以懒汉模式需要加上锁synchronized (Singleton.class) 来控制类只允许被实例化一次。

如果不加锁并发的情况下会出现这种情况

加锁后就不会出现多个线程同时执行相同代码的情况,因为线程是按队列的形式执行的,只有当前一个线程执行完之后才能进入代码块。

懒汉模式加锁引起的性能问题

在上面的案例中,我们通过锁的方式保证了单例模式的安全性,因为获取对象的方法加锁,多人同时访问只能排队等上一个人执行完才能继续执行,但加锁的方式会严重影响性能。

解决方案一:双重检查加锁(DCL)
public static Singleton getInstance() {
if (singleton == null) {//先验证对象是否创建

synchronized (Singleton.class) {//只有当对象未创建的时候才上锁

if (singleton == null) {
singleton = new Singleton();
}

}
}
return singleton;
}

双检测锁定的方式 是只有当对象未创建的时候才对请求加锁,对象创建以后都不会上锁,这样有效的提升了程序的效率,也可以保证只会创建一个对象的实例。

DCL是完美的解决了单例模式中性能和资源浪费的问题,但是DCL在并发情下也会存在一个问题,因为Jvm指令是乱序的;

情况如下:

线程1调用getInstance 获取对象实例,因为对象还是空未进行初始化,此时线程1会执行new Singleton()进行对象实例化,而当线程1的进行new Singleton()的时候JVM会生成三个指令。

指令1:分配对象内存。

指令2:调用构造器,初始化对象属性。

指令3:构建对象引用指向内存。

因为编译器会自作聪明的对指令进行优化, 指令优化后顺序会变成这样:

1、执行指令1:分配对象内存,

2、执行指令3:构建对象引用指向内存。

3、然后正好这个时候CPU 切到了线程2工作,而线程2此时也调用getInstance获取对象,那么线程2将执行下面这个代码 if (singleton == null),此时线程2发现对象不为空(因为线程1已经创建对象引用并分配对象内存了),那么线程2会得到一个没有初始化属性的对象(因为线程1还没有执行指令2)。

所以在这种情况下,双检测锁定的方式会出现DCL失效的问题。

解决方案二:用内部类实现懒汉模式

public class Singleton {

private Singleton() {
}

public static Singleton getInstance() {
return SingletonHoler.singleton;
}

//定义静态内部类
private static class SingletonHoler {
//当内部类第一次访问时,创建对象实例
private static Singleton singleton = new Singleton();
}

}
静态内部类原理:

当外部内被访问时,并不会加载内部类,所以只要不访问SingletonHoler 这个内部类, private static Singleton singleton = new Singleton() 不会实例化,这就相当于实现懒加载的效果,只有当SingletonHoler.singleton 被调用时访问内部类的属性,此时才会将对象进行实例化,这样既解决了恶汉模式下可能造成资源浪费的问题,也避免了了懒汉模式下的并发问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值