单例模式(Singleton Pattern):通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例),该类负责创建自己的对象,同时确保只有单个对象被创建。
注:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
Java中两种构建方式:
懒汉式—线程不安全:最基础的实现方式,线程上下文单例,不需要共享给所有线程,也不需要加synchronize之类的锁,以提高性能。这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
懒汉式—线程安全:加上synchronize之类保证线程安全的基础上的懒汉模式,相对性能很低,大部分时间并不需要同步饿汉方式。指全局的单例实例在类装载时构建。
饿汉式:这种方式比较常用,但容易产生垃圾对象。优点:没有加锁,执行效率会提高。缺点:类加载时就初始化,浪费内存。基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果
双检锁/双重校验锁(DCL,即 double-checked locking):在懒汉式基础上利用synchronize关键字和volatile关键字确保第一次创建时没有线程间竞争而产生多个实例,仅第一次创建时同步,性能相对较高。
登记式/静态内部类:作为创建类的全局属性存在,创建类被装载时创建。
枚举:java中枚举类本身也是一种单例模式
应用场景包括需要控制实例数量的类,如数据库连接池、配置管理、日志记录等。单例模式的主要优点包括防止多次实例化、节省资源、提供全局访问点以及减少全局状态的复杂性。然而,它也有一些缺点,比如可能导致代码难以测试和维护,以及在多线程环境中可能存在线程安全问题。
单例模式的实现通常包括将构造方法设置为私有,以防止外部代码创建实例。它还可能包括一个私有的静态实例成员,以及一个公共的静态方法用于获取这个实例。此外,双重检查锁定(double-checked locking)是一种常用的实现线程安全单例的方法。
示例:
//懒汉式,线程不安全
public class Singleton {private static Singleton instance;private Singleton(){}public static Singleton getInstance(){if (instance==null){instance=new Singleton();}return instance;}
}
//懒汉式 线程安全
public class Singleton {private static Singleton instance;public Singleton() {}public static synchronized Singleton getInstance(){if (instance==null){instance=new Singleton();}return instance;}
}
//饿汉式 线程安全
public class Singleton {private static Singleton instance=new Singleton();public Singleton() {}public static Singleton getInstance(){return instance;}
}
//双检锁/双重校验锁(DCL,即 double-checked locking)
public class Singleton {private volatile static Singleton singleton;public Singleton() {}public static Singleton getSingleton(){if (singleton==null){synchronized (Singleton.class){if (singleton==null){singleton=new Singleton();}}}return singleton;}
}//登记式/静态内部类
public class Singleton {private static class SingletonHolder{private static final Singleton INSTANCE=new Singleton();}public Singleton() {}public static final Singleton getInstance(){return SingletonHolder.INSTANCE;}
}
//枚举
public enum Singleton {INSTANCE;public void test(){System.out.println("hi~");}
}
//CAS「AtomicReference」(线程安全)
public class Singleton {private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();private Singleton() {}public static final Singleton getInstance() {for (; ; ) {Singleton instance = INSTANCE.get();if (null != instance) return instance;INSTANCE.compareAndSet(null, new Singleton());return INSTANCE.get();}}public static void main(String[] args) {System.out.println(Singleton.getInstance()); System.out.println(Singleton.getInstance()); }
}
注:一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式
增加:CAS「AtomicReference」(线程安全)
java并发库提供了很多原子类来支持并发访问的数据安全性;AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference。
AtomicReference 可以封装引用一个V实例,支持并发访问如上的单例方式就是使用了这样的一个特点。
使用CAS的好处就是不需要使用传统的加锁方式保证线程安全,而是依赖于CAS的忙等算法,依赖于底层硬件的实现,来保证线程安全。相对于其他锁的实现没有线程的切换和阻塞也就没有了额外的开销,并且可以支持较大的并发性。
当然CAS也有一个缺点就是忙等,如果一直没有获取到将会处于死循环中。