一、什么是单例模式
单例:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的软件设计模式之一,其目的是保证整个应用中只存在类的唯一个实例。
比如我们在系统启动时,需要加载一些公共的配置信息,对整个应用程序的整个生命周期中都可见且唯一,这时需要设计成单例模式。如:spring容器,session工厂,缓存,数据库连接池等等。
二、单例的好处
单例设计模式是指在确保一个类中最多会有一个实例,单例类要自己创建其唯一的实例,
并暴露给其他对象使用,也就是提供一个全局访问点。应用场景大概有2种:
某类的单例模式可以控制它的对象的使用,了解对象在哪里使用和使用的次数。
-
该类只存在一个实例,节省系统资源;对于需要频繁创建销毁的对象,使用单例模式可以提高系统性能
-
在程序中有些对象我们只需要一个,比如涉及线程池、缓存、硬件设备以及日志信息等,如果出现多个实例,会有造成冲突、结果的不一致性等问题。
-
缺点:不能外部实例化(new),调用人员不清楚调用哪个方法获取实例时会感到迷惑,尤其当看不到源代码时。
三、如何保证实例的唯一
-
防止外部初始化
-
由类本身进行实例化
-
保证实例化一次
-
对外提供获取实例的方法
-
线程安全
四、几种单例模式的比较
-
饿汉式单例模式
“因为饿,所以要立即吃饭,刻不容缓”,在定义类的静态私有变量同时进行实例化。
public class Singleton {
private static final Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}
①声明静态私有类变量,且立即实例化,保证实例化一次
②私有构造,防止外部实例化(通过反射是可以实例化的,不考虑此种情况)
③提供public的getInstance()方法供外部获取单例实例
好处:线程安全;获取实例速度快 缺点:类加载即初始化实例,内存浪费
-
懒汉式单例模式
“这个人比较懒,等用着你的时候才去实例化”,延迟加载。
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
优点:在获取实例的方法中,进行实例的初始化,节省系统资源
缺点:①如果获取实例时,初始化工作较多,加载速度会变慢,影响系统系能
②每次获取实例都要进行非空检查,系统开销大
③非线程安全,当多个线程同时访问getInstance()时,可能会产生多个实例
-
线程安全改造:同步锁
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
优点:线程安全,缺点:每次获取实例都要加锁,耗费资源,其实只要实例已经生成,以后获取就不需要再锁了
-
双重检查锁
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
优点:线程安全,进行双重检查,保证只在实例未初始化前进行同步,效率高 缺点:还是实例非空判断,耗费一定资源
-
静态内部类
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton singleton = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.singleton;
}
}
优点:既避免了同步带来的性能损耗,又能够延迟加载。
-
枚举类
public enum Singleton {
INSTANCE;
public void init() {
System.out.println("资源初始化。。。");
}
}
//调用
public class Main {
public static void main(String[] args) {
Singleton.INSTANCE.doSomething();
}
}
直接通过Singleton.INSTANCE.doSomething()的方式调用即可。方便、简洁又安全。天然线程安全,可防止反射生成实例。
「综上,所以比较推荐使用第五种和第六种实现方式。」