单例模式

单例模式

一、定义

单例模式:一个类在其生命周期内只有一个实例,该类能自行创建这个实例,所有获取该类实例的方法只能返回该实例,这种类设计的模式称为单例模式。

二、使用场景

单例模式使用条件:
1)有频繁实例化然后销毁的情况,也就是频繁的 new 对象,可以考虑单例模式;
2)创建对象时耗时过多或者耗资源过多,但又经常用到的对象;
3)频繁访问 IO 资源的对象,例如数据库连接池或访问本地文件;
单例模式使用场景:
1)Windows资源管理器,在Windows中只能打开一个资源管理器
2)Windows回收站,在Windows中只能打开一个回收站
3)网站计数器,所有用户在相同的时刻获取到的在线人数数量都是一致的,要实现这个需求,计数器就要全局唯一,也就正好可以用单例模式来实现。
4)应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
5)Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。Spring中@PropertySource 注解实现,默认就是单例模式。
6)数据库连接池的设计一般使用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因此使用单例模式来维护,就可以大大降低这种损耗。
7)多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
8)操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

三、单例模式实现方式

1)饿汉式(不推荐)
在类加载阶段就完成实例的初始化,通过类加载机制来保证线程安全。
程序中如果存在过多的饿汉式单例模式的实现,在系统启动时,会导致启动变慢。

public class HungrySingletonTest {
public static void main(String[] args) {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
System.out.println(hungrySingleton);
}
}

/**

  • 通过类加载机制来完成实例的初始化,加载的时候就初始化,不存在延迟加载
    */
    class HungrySingleton {
    private static HungrySingleton instance = new HungrySingleton();

    public static HungrySingleton getInstance() {
    return instance;
    }

    private HungrySingleton(){
    if (instance != null) {
    throw new RuntimeException(“singleton not allow more than one instance”); // 防止反射攻击
    }
    }
    }

2)懒汉式(可使用)
使用对象的时候加载,避免因对象加载过多引起系统启动慢,但容易出现线程安全问题。
package com.designpatterns.singleton;

/**

  • spring中ReactiveAdapterRegistry就是使用懒加载方式获取的单例对象
    */
    public class LazySingletonTest {
    public static void main(String[] args) {
    // LazySingleton lazySingleton = LazySingleton.getInstance();
    // System.out.println(lazySingleton);
    // LazySingleton lazySingleton2 = LazySingleton.getInstance();
    // System.out.println(lazySingleton2);

     new Thread(new Runnable() {
         @Override
         public void run() {
             LazySingleton lazySingleton = LazySingleton.getInstance();
             System.out.println(lazySingleton);
         }
     }).start();
     new Thread(new Runnable() {
         @Override
         public void run() {
             LazySingleton lazySingleton = LazySingleton.getInstance();
             System.out.println(lazySingleton);
         }
     }).start();
    

    }
    }

/**

  • 一个jvm只有一个实例
    */
    class LazySingleton {

    private static volatile LazySingleton instance; // volatile关键字修饰的变量执行的内存空间,操作时会防止指令重排序

    private LazySingleton(){
    if (instance != null) {
    throw new RuntimeException(“singleton not allow more than one instance”); // 防止反射攻击
    }
    }

    /**

    • 方法上使用synchronized是可以完成单例的,但是此处使用synchronized太重,影响性能,且没有必要
    • @return
      /
      public static LazySingleton getInstance() {
      if (instance == null) {
      synchronized (LazySingleton.class) {
      if (instance == null) { // 双重校验
      // 存在指令重排序 JIT、编译器、CPU
      /
      *
      new 一个对象时,原子操作有三步,1、分配空间->返回一个指向该空间的内存引用2、对空间进行初始化3、把内存引用赋值给引用变量
      其中2、3两步存在指令重排序的问题,如果两个线程进入到单例获取方法,其中一个锁住类后,先做了赋值操作,后做初始化,另一个线程
      判断对象是否存在时,由于第一个线程已经给变量赋值,所以第二个线程就获得一个没有初始化的对象,此时使用时会报空指针问题。故需要
      加上一个关键字volatile
      注意点:1)线程安全 2)防止指令重排序 3)双重检查优化
      */
      instance = new LazySingleton(); // 该行操作不是原子的
      // 1、分配空间,返回空间引用2、初始化3、引用赋值给变量
      }
      }
      }
      return instance;
      }
      }

3)枚举
使用枚举实现单例不存在安全问题,可自动防御反射攻击
推荐使用:线程安全、懒加载、性能好
public enum EnumSingleton {
INSTANCE;

public void test(){
    System.out.println("hello singleton");
}

}

public class MainTest {
public static void main(String[] args) {
EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
enumSingleton.test();
}
}

4)静态内部类
推荐使用:线程安全、懒加载、性能好

public class InnerClassSingletonTest {

public static void main(String[] args) {
    InnerClassSingleton innerClassSingleton = InnerClassSingleton.getInstance();
    System.out.println(innerClassSingleton);
}

}

class InnerClassSingleton{

static class InnerClass{
    private static InnerClassSingleton instance = new InnerClassSingleton();
}

public static InnerClassSingleton getInstance() {
    return InnerClass.instance;
}

private InnerClassSingleton(){
    if (InnerClass.instance != null) {
        throw new RuntimeException("singleton not allow more than one instance"); // 防止反射攻击
    }
}

}

单例模式尽量不要实现序列化接口,如果单例类实现了序列化接口Serializable, 就可以通过反序列化破坏单例,所以我们可以不实现序列化接口,如果非得实现序列化接口,可以重写反序列化方法readResolve(), 反序列化时直接返回相关单例对象。

除枚举外,其他单例模式都需要防止反射攻击

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值