- Published on
singleton 单例模式
单例的定义:
单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对 象(或者叫实例), 那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
- 饿汉式
- 懒汉式
- 双重检测
- 静态内部类
- 枚举
单例类在老进程中存在且只能存在一个对象,在新进程中也会存在且只能存在一个对 象。而且,这两个对象并不是同一个对象,这也就说,单例类中对象的唯一性的作用范围是 进程内的,在进程间是不唯一的。
常见的场景
- 比如说,你自定义了一个框架,自定义了一份xml格式的一个配置文件,你要读取这个配置文件,这个配置文件中的数据,读取到类中,这个类的实例,只要保存一份就可以。那么此时可以使用单例模式,将这个类做成他的实例只能有一个,在这个实例中保存了配置文件中的数据。
- 类似于我们之前给大家讲解的那个工厂模式,有些工厂是需要实例化对象的,要基于实例化对象,来实现继承、接口、实现等功能,工厂实例可以做成单例的,就可以了。
- 你自己去判断,如果是一个类的实例只需要保持一份,那就做成单例。
package wang.jinggo.basics.zhss.singleton;
/**
* 饿汉模式
* @author: wangyj
* @create: 2021-10-20
* @version: 1.0.0
**/
public class SafeFullSingletonPatternDemo {
public static class Singleton {
private static Singleton instance;
private Singleton() {
}
// 不是完美的
// 因为不同的JVM的编译器的问题,可能导致说,这个情况下,还是线程不安全的
// 具体的我不再这儿讲,因为涉及到复杂的JVM内部的原理
public static Singleton getInstance() {
// 如果线程1和线程2都执行到了这一步,然后此时线程1判断发现还是null
// 线程2此时判断发现instance == null,也会进去
if(instance == null) {
// 线程1就会进来,此时线程1停止,切换到线程2
// 线程2也会进来,此时切换到线程1
// 线程1,发现这里需要加锁, 在这里加锁,获取到了这个锁
// 线程2过来,线程2发现说,我也想要在这里加锁,发现说这个锁被人加了,线程2挂起等待别人释放锁
// 此时切换回线程2,线程2发现锁被释放,然后在这里加锁
synchronized(SafeFullSingletonPatternDemo.class) {
// 线程1就进来了,此时切换到线程2
// 切换回线程1,线程1此时在这里,再次判断,instance == null
// 线程2就进来了,double check,如果这里没有instance == null的判断,那么线程2就会再次创建
// 一个实例
// 但是这里是双重检查,线程2又判断了一下,instance == null?否,不是null
if(instance == null) {
// 线程1就会进来,创建一个实例
instance = new Singleton();
}
}
}
// 这边出来以后,线程1就释放锁了
// 线程2跳出来,直接获取一个instance返回了,这个instance就是之前线程1创建的实例
return instance;
}
}
}
package wang.jinggo.basics.zhss.singleton;
/**
* 线程不安全的饱汉模式
*
* @author: wangyj
* @create: 2021-10-20
* @version: 1.0.0
**/
public class UnsafeFullSingletonPatternDemo {
/**
* 线程不安全
*
* @author zhonghuashishan
*/
public static class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
/*
* 假设有两个线程过来
*
* 线程的基础:线程是并发着执行的,cpu,先执行一会儿线程1,然后停止执行线程1;切换过去执行线程2
* 执行线程2一会儿之后,再停止执行线程2;回来继续执行线程1
*
* 第一个线程,判断发现说instance == null,代码就进入到了下面去
* 第二个线程,执行到这儿,发现,此时instance == null,那么就没什么问题了,继续往下走
*
*/
if (instance == null) {
// 第一个线程跑到了这儿来,但是此时第一个线程,还没有执行下面的那行代码
// 此时,第二个线程代码也执行到了这儿,cpu切换回线程1
// 执行线程1的代码,线程1会创建一个实例出来
// 但是切换到线程2去执行的时候,线程2,的代码已经执行到这儿来了,此时又会再一次执行下面的代码
// 就是会 再一次创建一个实例,之前线程1创建的那个实例,就会被垃圾回收,废弃掉了
instance = new Singleton();
}
return instance;
}
}
}
package wang.jinggo.basics.zhss.singleton;
/**
* 这个才是我们实际开发过程中,最最常用的单例模式,内部类的方式来实现
*
* @author: wangyj
* @create: 2021-10-20
* @version: 1.0.0
**/
public class InnerClassFullSingletonPatternDemo {
/**
* 可以做饱汉模式
*
* 内部类,只要没有被使用,就不会初始化,Singleton的实例就不会创建
*
* 在第一次有人调用getInstance方法的时候,内部类会初始化,创建一个Singleton的实例
*
* 然后java能确保的一点是,类静态初始化的过程一定只会执行一次
*
*/
public static class Singleton {
private Singleton() {
}
public static class InnerHolder {
public static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return InnerHolder.instance;
}
}
}
package wang.jinggo.basics.zhss.singleton;
/**
* @author: wangyj
* @create: 2021-10-20
* @version: 1.0.0
**/
public class HungrySingletonPatternDemo {
public static void main(String[] args) {
Singleton singleton = Singleton.getInstance();
singleton.execute();
new Singleton();
}
public static class Singleton {
/**
* 第一步:直接就是将这个类的实例在创建出来,赋予static final修饰的变量
* <p>
* static:就是一个类的静态变量
* final:这个变量的引用第一次初始化赋予之后,就再也不能修改引用了
*/
private static final Singleton instance = new Singleton();
/**
* 第二步:将构造函数搞成private私有的
* <p>
* 此时除了这个类自己本身,其他任何人都不能创建它的这个实例对象
*/
private Singleton() {
}
/**
* 第三步:给一个static静态方法,返回自己唯一的内部创建的一个实例
*
* @return
*/
public static Singleton getInstance() {
return instance;
}
public void execute() {
System.out.println("单例类的方法");
}
}
}
最简单的一种类了,就是如果一个类就只需要一次,那么就使用这个类了。
但是使用单例模式有一个要求,不允许这个类的逻辑过于复杂,一般就是持有某份配置文件的配置,或者是别的一些数据。
因为如果别的很多类用了这个类,是没法打桩注入的,很麻烦。
所以只能是简单的情况下,用单例模式,就是持有一份数据,但是这份数据全局就只要一份,比如说一些配置数据,就用单例模式,或者是类似redis的客户端实例,或者是类似elasticsearch的客户端实例。