设计模式-单例模式

定义

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

类图

类型

饿汉式

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

复制代码
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的生命周期伴随着整个进程的周期。

相关推荐
老码观察1 小时前
设计模式实战解读(九):责任链模式——流水线上层层把关的艺术
java·设计模式·责任链模式
workflower2 天前
具身智能研究对象:物理交互中的智能行为
设计模式·动态规划·软件工程·软件构建·scrum
折哥的程序人生 · 物流技术专研2 天前
Java 23 种设计模式:从踩坑到精通 | 抽象工厂 —— 支付/收款如何成套创建?跨平台 UI 如何一键换肤?
java·开发语言·后端·设计模式
老码观察2 天前
设计模式实战解读(八):代理模式——控制访问的隐形中间层
设计模式·代理模式
我爱cope2 天前
【Agent智能体12 | 反思设计模式-使用外部反馈】
人工智能·设计模式·语言模型·职场和发展
geovindu2 天前
python: Bounded Parallelism Pattern
开发语言·python·设计模式·有界并行模式
我爱cope2 天前
【Agent智能体11 | 反思设计模式-评估反射的影响的方法】
人工智能·设计模式·语言模型·职场和发展
nnsix2 天前
设计模式 - 迭代器模式 笔记
笔记·设计模式·迭代器模式
geovindu2 天前
go: Bounded Parallelism Pattern
开发语言·后端·设计模式·golang·有界并行模式