java单例模式五种实现方式

应用场景

由于单例模式只生成一个实例, 减少了系统性能开销(如: 当一个对象的产生需要比较多的资源时, 如读取配置, 产生其他依赖对象, 则可以通过在应用启动时直接产生一个单例对象, 然后永久驻留内存的方式来解决)

  • Windows中的任务管理器;
  • 文件系统, 一个操作系统只能有一个文件系统;
  • 数据库连接池的设计与实现;
  • Spring中, 一个Component就只有一个实例Java-Web中, 一个Servlet类只有一个实例;

实现要点

  • 声明为private来隐藏构造器
  • private static Singleton实例
  • 声明为public来暴露实例获取方法

单例模式主要追求三个方面性能

  • 线程安全
  • 调用效率高
  • 延迟加载

实现方式

主要有五种实现方式,懒汉式(延迟加载,使用时初始化),饿汉式(声明时初始化),双重检查,静态内部类,枚举。

饿汉式

这种方式基于类ClassLoder机制,使用static来定义静态成员变量或静态代码,让instance在类加载时就进行初始化;避免了同步问题,实现线程安全。饿汉式的优势在于实现简单,劣势在于不是懒加载模式(lazy initialization)

存在问题

  • 在需要实例之前就完成了初始化,在系统中单例场景较多的情况下,会造成内存占用,加载速度慢问题
  • 由于在调用getInstance()之前就完成了初始化,如果需要给getInstance()函数传入参数,将会无法实现
public class Singleton {
   
    private static final Singleton instance = new Singleton();
    private Singleton() {
   
    };
    public static Singleton getInstance() {
   
        return instance;
    }
}  
//static 静态代码块
public class Singleton {
   
    private Singleton instance = null;
    static {
   
        instance = new Singleton();
    }
    private Singleton (){
   }
    public static Singleton getInstance() {
   
        return this.instance;
    }
}

静态内部类

由于内部类不会在类的外部被使用,所以只有在调用getInstance()方法时JVM才会被加载静态内部类。同时依赖JVM的 ClassLoader 类加载机制保证了不会出现同步问题。

public class Singleton {
   
    private Singleton() {
   
    };
    public static Singleton getInstance() {
   
        return Holder.instance;
    }
    //静态内部类实现创建单例对象
    private static class Holder{
   
        private static Singleton instance = new Singleton();
    }
}  

枚举方法 实现单例

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒。

  • 线程安全

由于枚举类的会在编译期编译为继承自java.lang.Enum的类,其构造函数为私有,不能再创建枚举对象,枚举对象的声明和初始化都是在static块中,所以由JVM的ClassLoader机制保证了线程的安全性。但是不能实现延迟加载

  • 序列化

由于枚举类型采用了特殊的序列化方法,从而保证了在一个JVM中只能有一个实例。

枚举类的实例都是static的,且存在于一个数组中,可以用values()方法获取该数组
在序列化时,只输出代表枚举类型的名字属性 name
反序列化时,根据名字在静态的数组中查找对应的枚举对象,由于没有创建新的对象,因而保证了一个JVM中只有一个对象

public enum Singleton {
   
    INSTANCE;
    public String error(){
   
        return "error";
    }
} 

饿汉式,静态内部类,枚举类型方式实现单例模式 原理都是利用借助了类加载的时候初始化单例。即借助了ClassLoader的线程安全机制。这几种实现方式,虽然并没有显示的使用synchronized,但是还是其底层实现原理还是用到了synchronized。

所谓ClassLoader的线程安全机制,就是ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字,同步了多线程下的创建实例形式。也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的,也就是保证了线程安全。

懒汉式 实现单例

懒汉式,线程不安全的实现

由于没有同步,多个线程可能同时检测到实例没有初始化而分别初始化,从而破坏单例约束。

public class Singleton {
   
    private static Singleton instance;
    private Singleton() {
   
    };
    public static Singleton getInstance() {
   
        if (instance == null) {
   
            instance = new Singleton();
        }
        return instance;
    }
}  

懒汉式,线程安全但效率低下的实现 ,synchronized 静态同步方法

public class Singleton {
   
    private static Singleton instance;
    private Singleton() {
   
    };
    public static synchronized Singleton getInstance() {
   
        if (instance == null) {
   
            instance = new Singleton();
        }
        return instance;
    }
}  

由于对象只需要在初次初始化时需要同步,多数情况下不需要互斥的获得对象,加锁会造成巨大无意义的资源消耗

加双重检查机制

这种方法对比于上面的方法确保了只有在初始化的时候需要同步,当初始化完成后,再次调用getInstance不会再进入synchronized块。 内部检查是必要的,由于在同步块外的if语句中可能有多个线程同时检测到instance为null,同时想要获取锁,所以在进入同步块后还需要再判

  • 6
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值