设计模式-单例模式

定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

类图

类型

饿汉式

线程安全,调用效率高,但是不能延迟加载。

复制代码
public class HungrySingleton
{
    private static final HungrySingleton instance=new HungrySingleton();
    private HungrySingleton(){}
    public static HungrySingleton getInstance()
    {
        return instance;
    }
}

懒汉式

线程安全,调用效率不高,并且可以延迟加载。

复制代码
public class LazySingleton
{
    private static volatile LazySingleton instance=null;    //保证 instance 在所有线程中同步
    private LazySingleton(){}    //private 避免类在外部被实例化
    public static synchronized LazySingleton getInstance()
    {
        //getInstance 方法前加同步
        if(instance==null)
        {
            instance=new LazySingleton();
        }
        return instance;
    }
}

双重检查锁式

双重检查锁模式解决了单例、性能、线程安全问题。但在多线程情况下,可能会出现空指针问题。问题在于JVM 在实例化对象时会进行优化和指令重排序操作。解决空指针问题只需使用volatile 关键字,volatile 可以保证可见性和有序性。

复制代码
public class LazyMan3 {
    private LazyMan3(){}
    private static volatile LazyMan3 instance;
    public static LazyMan3 getInstance(){
        //第一次判断,如果instance不为null,不需要抢占锁,直接返回对象
        if (instance == null){
            synchronized (LazyMan3.class){
                //第二次判断
                if (instance == null){
                    instance = new LazyMan3();
                }
            }
        }
        return instance;
    }
}
class LazyMan3Test{
    public static void main(String[] args) {
        LazyMan3 instance = LazyMan3.getInstance();
        LazyMan3 instance1 = LazyMan3.getInstance();
        System.out.println(instance == instance1);
    }
}

静态内部类式

线程安全,调用效率不高,可以延迟加载;

复制代码
public class LazyMan4 {
    private LazyMan4(){}
    //定义一个静态内部类
    private static class LazyMan4Holder{
        private static final LazyMan4 INSYANCE = new LazyMan4();
    }
    //对外访问方法
    public static LazyMan4 getInstance(){
        return LazyMan4Holder.INSYANCE;
    }
}
class LazyMan4Test{
    public static void main(String[] args) {
        LazyMan4 instance = LazyMan4.getInstance();
        LazyMan4 instance1 = LazyMan4.getInstance();
        System.out.println(instance == instance1);
    }
}

静态代码块式

复制代码
public class HungryChinese2 {
    //私有构造方法,为了不让外界创建该类的对象
    private HungryChinese2(){}
 
    //声明该类类型的变量
    private static HungryChinese2 hungryChinese2;//初始值为null
 
    //静态代码块中赋值
    static {
        hungryChinese2 = new HungryChinese2();
    }
 
    //对外提供的访问方式
    public static HungryChinese2 getInstance(){
        return hungryChinese2;
    }
}
class HungryChinese2Test{
    public static void main(String[] args) {
        HungryChinese2 instance = HungryChinese2.getInstance();
        HungryChinese2 instance1 = HungryChinese2.getInstance();
        System.out.println(instance.equals(instance1));
    }
}

枚举式

线程安全,调用效率高,不能延迟加载,但是可以天然的防止反射和反序列化漏洞。

复制代码
public enum LazyMan5 {
    INSTANCE;
}
class LazyMan5Test{
    public static void main(String[] args) {
        LazyMan5 instance = LazyMan5.INSTANCE;
        LazyMan5 instance1 = LazyMan5.INSTANCE;
        System.out.println(instance == instance1);
    }
}

应用场景

  1. 某个类只要求生成一个对象的时候。

  2. 当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如Web中的配置对象、数据库的连接池等。

  3. 当某类需要频繁实例化,而创建的对象又频繁被销毁时,如多线程的线程池、网络连接池等。

单例问题

Serializable

  • 问题:如果单例类实现了java.io.Serializable 接口,那么这个类可能会被反序列化,并且反序列化多次使用同一对象时,会得到多个单例类的实例,这样就不是单例了;

  • 解决方法:需要添加如下方法

    // 反序列化时,如果定义了readResolve() 则直接返回此方法指定的对象,而不需要单独再创建新对象了;
    private Object readResovle() throws ObjectStreamException{
    // TODO Auto-generated method stub
    return instance;
    }

在Android 中使用单例模式可能会内存泄漏

  • 问题:当调用getInstance 时,如果传入的context是Activity 的context 。只要这个单例没有被释放,那么这个Activity 也不会被释放,一直到进程退出才会释放。

    public class CommUtils {

    复制代码
      private volatile static CommUtils mCommUtils;
    
      private Context mContext;
      public CommUtils(Context context) {
          mContext=context;
      }
    
      public static  CommUtils getInstance(Context context) {
          if (mCommUtils == null) {
              synchronized (CommUtils.class) {
                  if (mCommUtils == null) {
                      mCommUtils = new CommUtils(context);
                  }
              }
          }
          return mCommUtils;
      }

    }

  • 解决方法:能使用Application的Context就不要使用Activity的Context,Application的生命周期伴随着整个进程的周期。

相关推荐
willow10 小时前
Axios由浅入深
设计模式·axios
七月丶2 天前
别再手动凑 PR 了:这个 AI Skill 会按仓库习惯自动建分支、拆提交、提 PR
人工智能·设计模式·程序员
刀法如飞2 天前
从程序员到架构师:6大编程范式全解析与实践对比
设计模式·系统架构·编程范式
九狼2 天前
Flutter + Riverpod +MVI 架构下的现代状态管理
设计模式
静水流深_沧海一粟3 天前
04 | 别再写几十个参数的构造函数了——建造者模式
设计模式
StarkCoder3 天前
从UIKit到SwiftUI的迁移感悟:数据驱动的革命
设计模式
阿星AI工作室3 天前
给openclaw龙虾造了间像素办公室!实时看它写代码、摸鱼、修bug、写日报,太可爱了吧!
前端·人工智能·设计模式
_哆啦A梦4 天前
Vibe Coding 全栈专业名词清单|设计模式·基础篇(创建型+结构型核心名词)
前端·设计模式·vibecoding
阿闽ooo7 天前
中介者模式打造多人聊天室系统
c++·设计模式·中介者模式