这是我参与更文挑战的第22天,活动详情查看:更文挑战
一、什么是单例模式
1.概念
单例模式,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)
——百度百科
单例模式就是严格控制一个类的实例,要求在一个进程或一个线程中只存在一个实例,如果没有就创建一个实例返回,如果有就返回已经已经存在的实例。不同的单例模式都是为了确保只有一个实例。
2.常见的例子
像公司的公章,一般一个公司只有一个,这个公章就可以算是一个单例,只有真的丢失了才能去重新补办。
在举一个我之前开发遇到的案例,我写的那个系统需要从数据库读取一些配置信息,这些配置信息是计划在正式部署之后不会再改动的,就用到了单例模式。每次启动都去数据库中读取一次,然后封装成一个map单例,每个单体系统都去使用自己的单例。其实在开发的话,需要手动去添加、修改数据库配置还是很麻烦的?
3.特点
- 单例类在一个进程或线程中只能存在一个实例。
- 单例类的构造器必须私有化。不允许其它类来创建单例类,只有它自己能够创建自己。
- 单例类需要向其它类提供获取实例的方法,一般是静态的。
4.优点
- 减少了内存的开销。因为单例模式要做是严格控制只能存在一个实例,这样就会极大地减少实例反复创建时带来的内存开销。
- 更加容易管理和维护。没什么好说的,全部内容都写在这个类里,只需要修改这个类就行了。
5.缺点
- 扩展性不强。单例模式只专注于只有能存在一个实例,这就牺牲了它的扩展性。
- 与单一职责冲突。因为所有方法都要封装在单例类中,所以会越来越庞大,职责也会越来越不清晰。
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,都是一样的,但是它的写法就有点让人摸不着头脑。
三、总结
一般情况下,使用饿汉式和枚举类实现就可以了,如果要考虑延迟加载的话,就使用简单-懒汉式,再要考虑线程安全问题,就加个双重检查,或者直接使用静态内部类实现。























![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)