创建型模式-> 单例模式(SingleTon)
什么是单例模式
单例模式属于创建型设计模式,是Java种最简单的设计模式之一。
这种模式要求一个类仅有一个实例,并且提供了一个全局的访问点。在程序中多次使用同一个对象且作用相同时,所有需要调用的地方就会共享这个访问点(对象),从而防止因频繁创建对象而损耗系统性能。
优缺点
优点:
- 全局唯一实例:通过单例模式,可以确保一个类只有一个实例对象存在,全局范围内可以方便地访问该实例。
- 节省资源:由于只有一个实例存在,单例模式可以节省系统资源(如内存、CPU等),避免多次创建和销毁对象的开销。
- 避免对共享资源的竞争:在多线程环境中,单例模式可以避免对共享资源的竞争问题,确保数据一致性和线程安全性。
缺点:
- 不支持多态:单例模式一般只能创建一个固定类型的对象实例,不支持多态的灵活性。
- 降低了代码的灵活性:对于使用了单例模式的代码,如果需要改变实例化策略或使用其他类型的实例,可能需要修改代码和重新设计。
- 隐藏了依赖关系:单例模式可能隐藏了对其他组件或对象的依赖关系,使得代码的结构不够清晰,增加了代码的理解和维护的难度。
应用场景
生活场景
政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点。
Java场景
Bean定义的默认作用域: 在Spring中,默认情况下,所有的Bean都是单例的,也就是说 Spring 容器中只会创建一个特定类型的Bean实例。
Spring容器(ApplicationContext): Spring容器本身也是一个使用了单例模式的对象。无论是基于XML配置的ClassPathXmlApplicationContext,还是基于注解的AnnotationConfigApplicationContext,它们都是单例的,只会生成一个容器实例。
Spring AOP中的切面(Aspect): 在Spring AOP中,切面是用来定义横切关注点(如日志、事务等)的类。默认情况下,Spring会将切面定义为单例,以确保在整个应用程序中只有一个切面实例,以避免创建过多的代理对象。
创建单例
饿汉式(2种)
/**
* @author cris
* @className HungrySingleTon
* @description 饿汉:在类刚一初始化的时候就立即把单例对象创建出来
* @date 2024/06/29 08:53
**/
public class HungrySingleTon {
private HungrySingleTon() {}
private static HungrySingleTon instance = null;
static {
instance = new HungrySingleTon();
}
public static HungrySingleTon getInstance() {
return instance;
}
}
懒汉式(4种)
/**
* @author cris
* @className LazySingleTon
* @description 懒汉:懒加载,就是在需要的时候才回去创建对象
* @date 2024/06/29 08:57
**/
public class LazySingleTon {
private LazySingleTon() {}
private static LazySingleTon instance;
/**
* 1.单例模式【线程不安全,不推荐】
* 因为没有加锁synchronized,严格意义上不算单例。
*
* @return 实例
*/
public static LazySingleTon getInstance1() {
if (instance == null) {
instance = new LazySingleTon();
}
return instance;
}
/**
* 2.线程安全但效率低【不推荐】
* 99%的情况下不需要同步
*
* @return 实例
*/
public static synchronized LazySingleTon getInstance2() {
if (instance == null) {
instance = new LazySingleTon();
}
return instance;
}
/**
* 3.单例模式,线程不安全【不推荐】
* 虽然加了锁,但是等到第一个线程执行完instance2=new Singleton();跳出锁时
* 令一个线程恰好刚判断完instance2为null,此时又会加载另一个实例
*
* @return 实例
*/
public static LazySingleTon getInstance3() {
if (instance == null) {
synchronized (LazySingleTon.class) {
instance = new LazySingleTon();
}
}
return instance;
}
/**
* 4.双重校验锁:延迟加载+线程安全【推荐】
*/
private static volatile LazySingleTon instance4;
public static LazySingleTon getInstance4() {
if (instance4 == null) {
synchronized (LazySingleTon.class) {
if (instance4 == null) {
instance4 = new LazySingleTon();
}
}
}
return instance4;
}
}
静态内部类
/**
* @author cris
* @className StaticInnerSingleTon
* @description 静态内部类单例模式
* @date 2024/06/29 09:06
**/
public class StaticInnerSingleTon {
private static class StaticInnerSingleTonHolder {
private static final StaticInnerSingleTon INSTANCE = new StaticInnerSingleTon();
}
private StaticInnerSingleTon(){}
public static final StaticInnerSingleTon getInstance() {
return StaticInnerSingleTonHolder.INSTANCE;
}
}
枚举
/**
* @author cris
* @className SingleTon
* @description
* @date 2024/06/29 23:57
**/
public enum SingleTon {
INSTANCE;
public void whateverMethod() {
System.out.println("单例模式最佳实现方式");
}
}
总结
单例最常见的写法:懒汉式、饿汉式。
懒汉式:懒加载,在调用的时候才会实例化对象,推荐使用双重校验锁的方式。
饿汉式:类加载的时候就已经实例化好对象了,不会存在并发安全和性能问题。
在开发中,如果内存要求高,就用懒汉式,不高则用饿汉式。
最优雅的方式是使用枚举,代码极简,没有线程安全问题,又能防止反射和序列化时破坏单例。