【设计模式】面试官:请你手写一个单例模式!我:我要写五个!

这是我参与更文挑战的第22天,活动详情查看:更文挑战


?设计模式目录

一、什么是单例模式
1.概念

单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
——百度百科

单例模式就是严格控制一个类的实例,要求在一个进程或一个线程中只存在一个实例,如果没有就创建一个实例返回,如果有就返回已经已经存在的实例。不同的单例模式都是为了确保只有一个实例

2.常见的例子

像公司的公章,一般一个公司只有一个,这个公章就可以算是一个单例,只有真的丢失了才能去重新补办。

在举一个我之前开发遇到的案例,我写的那个系统需要从数据库读取一些配置信息,这些配置信息是计划在正式部署之后不会再改动的,就用到了单例模式。每次启动都去数据库中读取一次,然后封装成一个map单例,每个单体系统都去使用自己的单例。其实在开发的话,需要手动去添加、修改数据库配置还是很麻烦的?

3.特点
  1. 单例类在一个进程或线程中只能存在一个实例
  2. 单例类的构造器必须私有化。不允许其它类来创建单例类,只有它自己能够创建自己。
  3. 单例类需要向其它类提供获取实例的方法,一般是静态的。
4.优点
  1. 减少了内存的开销。因为单例模式要做是严格控制只能存在一个实例,这样就会极大地减少实例反复创建时带来的内存开销
  2. 更加容易管理和维护。没什么好说的,全部内容都写在这个类里,只需要修改这个类就行了。
5.缺点
  1. 扩展性不强。单例模式只专注于只有能存在一个实例,这就牺牲了它的扩展性。
  2. 与单一职责冲突。因为所有方法都要封装在单例类中,所以会越来越庞大,职责也会越来越不清晰。
6.原则

“+”代表遵守,“-”代表不遵守或者不相关

原则 开放封闭 单一职责 迪米特 里氏替换 依赖倒置 接口隔离 合成复用
+ +
二、单例模式的分类
1.饿汉式
概念

就像饿汉遇到美食一样,一下子就全部吃完。
饿汉式单例,就是在装载.class文件是就会在静态区生成一个实例,其它类都是调用这个类。

代码
package singletion;
/**
 * @author xxj
 * 饿汉单例
 */
public class HungrySingletion {
    private static HungrySingletion instance=new HungrySingletion();
    private HungrySingletion(){}
    public static HungrySingletion getInstance(){
        return instance;
    }
}
复制代码
特点

饿汉式就是简单粗暴,一上来就在静态区生成一个实例保证了线程安全,不会产生多余的实例,但是创建的实例会导致初始化比较缓慢。下一种单例模式就是解决了这个问题。

2.懒汉式
概念

懒汉嘛就是懒,不到火烧眉毛的时候绝对不会去做事情。
懒汉式单例,就是只在调用向其它类提供获取实例的方法时,才会去生成实例,但是这样就会存在一些线程问题。

简单-懒汉式

代码

package singletion;
/**
 * @author xxj
 * 懒汉式单例
 */
public class LazySingleton {
    private static LazySingleton instance=null;
    private LazySingleton(){}
    public static LazySingleton  getInstance(){
    	if(instance==null)
        instance=new LazySingleton();
        return instance;
    }
}
复制代码

特点

一开始instance指向的是null,所以它不会一上来就在静态区生成一个实例,而是延迟加载,但是它存在线程问题。一旦多个线程同时访问这个获取实例的方法时,就会出现创建多个实例的情况,那它在这种极端的情况下,就不能算是单例模式了,所以下一个懒汉模式解决了这个问题。

双重检查-懒汉式

代码

package singletion;
/**
 * @author xxj
 * 双重检查懒汉式单例
 */
public class DoubleCheckLazySingleton {
    private static volatile DoubleCheckLazySingleton instance =null;

    public static DoubleCheckLazySingleton getInstance() {
        if (instance==null){
            synchronized (DoubleCheckLazySingleton.class){
                if (instance==null){
                    instance=new DoubleCheckLazySingleton();
                }
            }
        }
        return instance;
    }
    private DoubleCheckLazySingleton() {
    }
}
复制代码

双重检查

简单点说,第一次检查是为了提高效率,毕竟并不是所有线程都能转这个空子,这一道检查,就可以拦截大部分的线程了;第二次检查才是真正地解决线程安全问题,这里加上了synchronized关键字,通过加锁的方式,就可以解决多线程问题了。

特点

继承了简单-懒汉式单例的延迟加载的特点,也解决了线程安全问题,唯一的缺点就是结构复杂了。

还有一种懒汉式单例模式,就是比双重检查-懒汉式单例少第二次检查,那种单例能解决少量线程的线程安全问题,线程数多了,就不能保证了。

下面是两种不常用的单例模式。

3.静态内部类
代码
public class Singleton {  
    private static class SingletonHolder {  
    	private static final Singleton instance = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.instance ;  
    }  
}
复制代码
解释

这种使用静态内部类实现的单例模式,其实是饿汉式的升级版,首先是在单例类的内部创建了一个静态内部类,内部类里面有一个单例类的实例然后单例类给其它类提供一个获取实例的方法
当其它类调用这个方法时,类加载器才会去装载内部类,一装载,就会生成一个单例类的实例

特点

不仅继承了饿汉式的线程安全的特性,而且实现了延迟加载的功能。

4.枚举类
代码
package singletion;
/**
 * @author xxj
 * 枚举类单例模式
 */
public enum  EnumSingleton {
    INSTANCE;
    public static EnumSingleton getInstance() {
        return EnumSingleton.INSTANCE;
    }
}
复制代码
特点

首先即使实现简单方便,用枚举类实现单例实际上和饿汉式是一样的,它可以保证线程安全,但是同样初始化比较缓慢
再说一点,枚举类的确可以实现单例化,我验证过hashCode,都是一样的,但是它的写法就有点让人摸不着头脑。

三、总结

一般情况下,使用饿汉式和枚举类实现就可以了,如果要考虑延迟加载的话,就使用简单-懒汉式,再要考虑线程安全问题,就加个双重检查,或者直接使用静态内部类实现。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享