定义
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点以确保所有代码都在使用相同的实例。单例模式的目的是限制类的实例化次数,通常用于确保系统中某个类的全局唯一性。
意图
- 确保唯一实例: 保证一个类仅有一个实例,并提供一个全局访问点。
- 全局访问点: 允许全局访问类的唯一实例,方便在程序的各个地方使用同一个对象。
举例
饿汉式
/**
* 提前初始化静态实例保证了线程安全。
*/
public final class IvoryTower {
/**
* 私有构造器
*/
private IvoryTower() {}
/**
* 实例化,类加载的时候会被初始化并赋值
*/
private static final IvoryTower INSTANCE = new IvoryTower();
public static IvoryTower getInstance() {
return INSTANCE;
}
}
测试
IvoryTower ivoryTower1 = IvoryTower.getInstance();
IvoryTower ivoryTower2 = IvoryTower.getInstance();
LOGGER.info("ivoryTower1={}", ivoryTower1);
LOGGER.info("ivoryTower2={}", ivoryTower2);
LOGGER.info("ivoryTower1==ivoryTower2:{}",ivoryTower1==ivoryTower2);
优点:
- 线程安全: 饿汉式在类加载时就创建实例,因此在多线程环境下不需要考虑线程安全问题。
- 简单直接: 实现简单,代码易于理解。
缺点:
- 资源浪费: 饿汉式在类加载时就创建了实例,如果该实例在后续的代码中没有被使用到,会造成资源的浪费。
- 不能懒加载: 由于在类加载时就创建实例,因此不能实现延迟加载,如果这个单例在后续的代码中一直没有被用到,就会白白占用内存。
懒汉式线程安全
public final class ThreadSafeLazyLoadedIvoryTower{
private static ThreadSafeLazyLoadedIvoryTower instance;
private ThreadSafeLazyLoadedIvoryTower() {
if (instance == null) {
instance = this;
} else {
throw new IllegalStateException("Already initialized.");
}
}
/**
* 同步方法保证只被初始化一次
*/
public static synchronized ThreadSafeLazyLoadedIvoryTower getInstance() {
if (instance == null) {
instance = new ThreadSafeLazyLoadedIvoryTower();
}
return instance;
}
}
测试
ThreadSafeLazyLoadedIvoryTower threadSafeIvoryTower1 =
ThreadSafeLazyLoadedIvoryTower.getInstance();
ThreadSafeLazyLoadedIvoryTower threadSafeIvoryTower2 =
ThreadSafeLazyLoadedIvoryTower.getInstance();
LOGGER.info("threadSafeIvoryTower1={}", threadSafeIvoryTower1);
LOGGER.info("threadSafeIvoryTower2={}", threadSafeIvoryTower2);
LOGGER.info("threadSafeIvoryTower1==threadSafeIvoryTower2:{}",threadSafeIvoryTower1==threadSafeIvoryTower2);
优点:
- 延迟加载: 实例只有在第一次使用时才会被创建,避免了资源的浪费。
- 适合多线程环境: 使用同步锁等机制可以保证在多线程环境下也能保持单例的唯一性。
缺点:
- 性能低下: 加锁机制导致在并发环境下性能会有所下降,因为每次获取实例都需要进行加锁和解锁操作。
枚举式
public enum EnumIvoryTower {
INSTANCE;
@Override
public String toString() {
return getDeclaringClass().getCanonicalName() + "@" + hashCode();
}
}
测试
EnumIvoryTower enumIvoryTower1 = EnumIvoryTower.INSTANCE;
EnumIvoryTower enumIvoryTower2 = EnumIvoryTower.INSTANCE;
LOGGER.info("enumIvoryTower1={}", enumIvoryTower1);
LOGGER.info("enumIvoryTower2={}", enumIvoryTower2);
LOGGER.info("enumIvoryTower1==enumIvoryTower2:{}",enumIvoryTower1==enumIvoryTower2);
优点:
- 线程安全: 枚举类的实例创建是线程安全的,因为枚举类的加载过程是由JVM在类加载阶段进行的,保证了线程安全。
- 防止反射攻击: 枚举类的构造方法是私有的,因此无法通过反射机制来创建枚举类的实例。
缺点:
- 不支持懒加载: 枚举类在加载时就会创建实例,不支持延迟加载,无法实现懒加载的特性。
双重检查锁
public final class ThreadSafeDoubleCheckLocking {
private static volatile ThreadSafeDoubleCheckLocking instance;
private ThreadSafeDoubleCheckLocking() {
if (instance != null) {
throw new IllegalStateException("Already initialized.");
}
}
public static ThreadSafeDoubleCheckLocking getInstance() {
ThreadSafeDoubleCheckLocking result = instance;
// 第一次检查,此时不加锁,如果不为空,返回实例
if (result == null) {
//同步对象
synchronized (ThreadSafeDoubleCheckLocking.class) {
result = instance;
//第二次检查,此时虽然没有其他线程进入同步代码块,但是有可能到这里的时候其他线程已经初始化了。因为第一次判断的时候可能其他线程正在初始化,判断完成后正好初始化完成,instance定义为volatile保证了线程对instance的可见性
if (result == null) {
instance = result = new ThreadSafeDoubleCheckLocking();
}
}
}
return result;
}
}
测试
ThreadSafeDoubleCheckLocking dcl1 = ThreadSafeDoubleCheckLocking.getInstance();
LOGGER.info(dcl1.toString());
ThreadSafeDoubleCheckLocking dcl2 = ThreadSafeDoubleCheckLocking.getInstance();
LOGGER.info(dcl2.toString());
LOGGER.info("dcl1==dcl2:{}",dcl1==dcl2);
优点:
- 延迟加载: 双重检查锁定允许在需要时延迟初始化单例对象,避免了一开始就创建实例。
- 提高性能: 在实例已经被创建的情况下,避免了每次获取实例时都进入同步代码块,提高了性能。
缺点:
- 实现复杂: 双重检查锁定的实现相对复杂,需要考虑线程安全、指令重排序等并发问题,容易出错。
静态内部类
public final class InitializingOnDemandHolderIdiom {
private InitializingOnDemandHolderIdiom() {}
public static InitializingOnDemandHolderIdiom getInstance() {
return HelperHolder.INSTANCE;
}
private static class HelperHolder {
private static final InitializingOnDemandHolderIdiom INSTANCE =
new InitializingOnDemandHolderIdiom();
}
}
测试
InitializingOnDemandHolderIdiom demandHolderIdiom =
InitializingOnDemandHolderIdiom.getInstance();
LOGGER.info(demandHolderIdiom.toString());
InitializingOnDemandHolderIdiom demandHolderIdiom2 =
InitializingOnDemandHolderIdiom.getInstance();
LOGGER.info(demandHolderIdiom2.toString());
LOGGER.info("demandHolderIdiom==demandHolderIdiom2:{}",demandHolderIdiom==demandHolderIdiom2);
优点:
- 延迟加载: 静态内部类只有在被使用时才会被加载,因此可以实现延迟加载。
- 线程安全: 静态内部类在加载时是线程安全的,JVM会保证只加载一次,避免了多线程并发访问的问题。
- 简单明了: 实现简单,代码清晰易懂。
缺点:
- 不适用于某些场景: 如果单例的实例化需要处理复杂的资源管理或者有复杂的初始化过程,静态内部类可能不够灵活。