设计模式--单例模式
单例模式在各个框架中设计或者实际开发经常遇到,主要是保证一个类只有一个示例,减少常用类频繁创建带来的性能损失。
常见的场景有:
- Spring中的Bean
- 项目中全局的属性
- 数据库连接池
Static关键字
一个简单的解决方式是使用static关键字,类加载过程在可以保证没有示例的情况下也能使用类的属性和方法。
public class Singleton_00 {
public static Map<String,String> cache = new ConcurrentHashMap<String, String>();
}
- 这种方式在初始化的过程中会直接进行加载,导致启动缓慢
- 静态的方法内部不能直接调用类里的其它方法
懒汉模式(线程不安全)
使用时创建类的示例,但是在多线程中不安全,存在多线程同时创建的风险。
public class Singleton_01 {
private static Singleton_01 instance;
private Singleton_01() {
}
public static Singleton_01 getInstance(){
if (null != instance) return instance;
instance = new Singleton_01();
return instance;
}
}
- 将构造函数私有化
懒汉模式(线程安全)
对创建方法进行加锁
public class Singleton_02 {
private static Singleton_02 instance;
private Singleton_02() {
}
public static synchronized Singleton_02 getInstance(){
if (null != instance) return instance;
instance = new Singleton_02();
return instance;
}
}
饿汉模式(线程安全)
程序启动时直接加载创建类实例,非懒加载
public class Singleton_03 {
private static Singleton_03 instance = new Singleton_03();
private Singleton_03() {
}
public static Singleton_03 getInstance() {
return instance;
}
}
懒汉式-使用类的内部类(线程安全)
public class Singleton_04 {
private static class SingletonHolder {
private static Singleton_04 instance = new Singleton_04();
}
private Singleton_04() {
}
public static Singleton_04 getInstance() {
return SingletonHolder.instance;
}
}
- 关于使用静态内部类,必须清楚一个类的加载顺序。静态方法>静态代码块>静态内部类静态代码块>静态内部类静态方法。
- 静态属性加载时创建,而静态方法(包括静态内部类)要在创建使用时才会调用。
- 借用JVM虚拟机保证多线程并发的正确性来确保线程安全,并且符合懒加载, 也不会因为加锁耗费性能
- 推荐使用
懒汉模式-双重锁校验(线程安全)
和加锁的懒汉模式差不多,只是把锁放在了示例的部分,减少了获取锁的耗时。
public class Singleton_05 {
private static volatile Singleton_05 instance;
private Singleton_05() {
}
public static Singleton_05 getInstance(){
if(null != instance) return instance;
synchronized (Singleton_05.class){
if (null == instance){
instance = new Singleton_05();
}
}
return instance;
}
}
- volatile是一种轻量同步机制,可以确保变量在多线程的可见性和禁止指令重排序(分配空间、指向空间、初始化对象,顺序固定)
- synchronized (Singleton_05.class)保证了的多个实例线程都是访问的同一个对象
懒汉模式-CAS(线程安全)
CAS(Compare and Swap)是一种用于实现无锁算法的原子操作,广泛应用于并发编程中。CAS能够保证操作的原子性。
public class Singleton_06 {
private static final AtomicReference<Singleton_06> instance = new AtomicReference<>();
// 私有构造函数
private Singleton_06() { }
public static Singleton getInstance() {
// 尝试使用CAS原子操作设置实例
Singleton singleton = instance.get();
if (singleton == null) {
singleton = new Singleton_06();
if (instance.compareAndSet(null, singleton)) {
return singleton;
} else {
return instance.get(); // 另一个线程已经创建了实例,返回它
}
}
return singleton;
}
}
- CAS不需要加锁,所以性能高
- 但CAS存在ABA问题和长轮询问题
饿汉式-枚举模式实现(线程安全)
Java 枚举类型本质上是线程安全的,并且提供了序列化机制,因此可以很自然地实现单例,而无需考虑线程安全或反序列化的问题。 并且所有的枚举实例在类加载时就被创建和初始化,这些实例是固定的,不能改变或被扩展。
public enum Singleton_07 {
INSTANCE;
// 可以定义实例方法
public void someMethod() {
System.out.println("Doing something in Singleton instance");
}
}
总结
单例模式是最常用的一种模式,但包含了很多关于JAVA锁、并发、静态类、内部类、线程安全等知识。