单例模式的定义
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象;一个最好的办法就是:让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且提供一个访问该实例的方法。
单例模式的实现
饿汉式
- 构造函数私有化
- 迫不及待创建静态对象
- 公有方法返回对象
package ASingleTon;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/6/26 0026 20:58
* 饿汉式单例
* 可能会浪费空间
*/
public class Hungry {
//可能会浪费大量的内存空间
private int[] a = new int[1024];
private int[] a1 = new int[1024];
//1、构造器私有化
private Hungry(){}
private final static Hungry HUNGRY = new Hungry();
//
public static Hungry getInstacne(){
return HUNGRY ;
}
}
可能会造成内存的浪费,上来加载大量对象到内存
可能被反射破坏
懒汉式 --DCL+volitale
DCL:双重检测
voliatle
静态关键字
package ASingleTon;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/6/26 0026 21:01
*
* DCL懒汉式单例:可以使用反射破坏
*/
public class LazyMan {
//构造器私有化
private LazyMan(){}
//加入volitale防止指令重拍
private static volatile LazyMan lazyMan;
//DCL:双重检测模式下啊的懒汉单例模式
public static LazyMan getInstance() {
if(lazyMan==null)
synchronized (LazyMan.class){
if(lazyMan==null){
//这个不是原子性操作
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*/
lazyMan = new LazyMan();
}
}
return lazyMan;//此时lazyman 还没有完成
}
//反射破解
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
//无视私有构造器
constructor.setAccessible(true);
LazyMan lazyMan1 = constructor.newInstance();
System.out.println(lazyMan1);//ASingleTon.LazyMan@4554617c
System.out.println(instance);//ASingleTon.LazyMan@74a14482
}
}
由于饿汉式,即静态初始化的方式,它是类一加载就实例化的对象,所以要提前占用系统资源;而懒汉式,又会面临着多线程访问的安全性问题,需要做双重锁定这样的处理才可以保证安全
静态内部类
package ASingleTon;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/6/26 0026 21:11
* 静态内部类实现
*
* 这几种方法都是不安全的
*/
public class Holder {
//构造器私有化
private Holder(){}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
枚举实现(反射不能破坏)
package ASingleTon;
/**
* @Author Zhou jian
* @Date 2020 ${month} 2020/6/26 0026 21:18
* 枚举本身也是一个Class类
* 枚举是线程安全的,并且反射不能破坏枚举
*/
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
单例模式的使用场景
下面几个场景中使用单例模式:
- 有频繁实例化然后销毁的情况,也就是频繁的 new 对象,可以考虑单例模式
- 创建对象时耗时过多或者耗资源过多,但又经常用到的对象;
- 频繁访问 IO 资源的对象,例如数据库连接池或访问本地文件;
配置文件访问类
项目中经常需要一些环境相关的配置文件,比如短信通知相关的、邮件相关的。比如
properties
文件,这里就以读取一个properties
文件配置为例,如果你使用的Spring ,
可以用@PropertySource
注解实现,默认就是单例模式。如果不用单例的话,每次都要 new 对象,每次都要重新读一遍配置文件,很影响性能,如果用单例模式,则只需要读取一遍就好了。
数据库连接池的实现
数据库连接池的实现,也包括线程池。为什么要做池化,是因为新建连接很耗时,如果每次新任务来了,都新建连接,那对性能的影响实在太大。所以一般的做法是在一个应用内维护一个连接池,这样当任务进来时,如果有空闲连接,可以直接拿来用,省去了初始化的开销。所以用单例模式,正好可以实现一个应用内只有一个线程池的存在,所有需要连接的任务,都要从这个连接池来获取连接。如果不使用单例,那么应用内就会出现多个连接池,那也就没什么意义了。如果你使用 Spring 的话,并集成了例如 druid 或者 c3p0 ,这些成熟开源的数据库连接池,一般也都是默认以单例模式实现的。