在日常开发过程中,有一些对象其实我们只需要一个,比如说:线程池、缓存、注册表、日志对象等。事实上,这些对象只能存在一个实例,如果同时存在多个实例的话,就会导致很多问题产生,例如:程序的行为异常、资源使用过量,或者是不一致的结果。
一般我们实例化一个对象的时候,会调用到该类的构造方法,当我们没有显式的定义构造函数,那么类会默认帮我们生成一个空的公共的构造函数,以便外界可以随时实例化该类。但当我们想要实现一个单例模式的时候,我们需要将公有的构造方法变成私有方法,禁止外界实例化对象。
接下来给大家介绍单例模式的五种实现方式:
一、饿汉式(不推荐)
//饿汉式
public class HungrySingleton {
private static HungrySingleton instance = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return instance;
}
}
饿汉式,顾名思义就是很饿的汉子,迫不及待的在定义单例对象的时候就生成了实例化对象,这样的做法导致可能在程序并没有用到该实例的时候就已经生成了对应的实例,浪费了资源。
二、懒汉式(不推荐)
//懒汉式
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton(){}
public static synchronized LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
懒汉式较饿汉式使用了懒加载的方法将对象的实例化放在了同步方法中,而不是一开始就实例化对象,使用synchronized保证了在多线程环境下,在同一时间点,有且只有一个线城能够访问该方法,从而生成唯一的单例实例化对象。
三、双重锁检查实现(不推荐)
//双重锁判断机制单例模式
public class DoubleCheckSingleton {
private static DoubleCheckSingleton instance;
private DoubleCheckSingleton(){}
public static DoubleCheckSingleton getInstance(){
if(instance == null){
synchronized (DoubleCheckSingleton.class) {
if(instance == null){
instance = new DoubleCheckSingleton();
}
}
}
return instance;
}
}
双重锁检查实现的单例模式代码较懒汉式把锁从方法上移到代码块上,性能上更优。因为如果synchronized放在方法上面,就是对整个方法进行同步,就算实例化对象已经创建好了,不同的线程也要竞争式调用该方法,这样使得调用性能降低。所以使用了两次检查机制,第一次是非同步代码块中进行判断,如果为空,再竞争式进入同步代码块,进入了之后再次判断,这样就在保证了性能的同时,也保证了有且仅有一个单例对象被实例化。
四、静态内部类实现(推荐)
//静态内部类实现的单例模式
public class StaticInteriorSingleton {
private static class InnerInstance{
private static final StaticInteriorSingleton instance = new StaticInteriorSingleton();
}
private StaticInteriorSingleton(){}
public static StaticInteriorSingleton getInstance(){
return InnerInstance.instance;
}
}
以静态内部类的方式来创建单例对象,利用了静态类的一个特点,在使用的时候才会进行加载,在第一次加载StaticIinteriorSingleton类的时候并不会实例化对象,只有当调用getIinstance方法的时候才会实例化对象,同时没有加锁带来的性能上的问题。
五、枚举实现
//用枚举实现的单例模式
enum EnumSingleton {
INSTANCE;
public void printMethod(){
System.out.println("实例中的方法");
}
}
public class EnumSingletonTest{
public static void main(String[] args) {
System.out.println((EnumSingleton.INSTANCE instanceof EnumSingleton));
System.out.println(EnumSingleton.INSTANCE.hashCode());
System.out.println(EnumSingleton.INSTANCE.hashCode());
System.out.println(EnumSingleton.INSTANCE.hashCode());
System.out.println(EnumSingleton.INSTANCE.hashCode());
EnumSingleton.INSTANCE.printMethod();
}
}
枚举我们平常可能很少用到,enum 的全称为 enumeration, 是 JDK 1.5 中引入的新特性,存放在 java.lang 包中。通常我们会在遍历或switch等用到, enum 的语法结构尽管和 class 的语法不一样,但是经过编译器编译之后产生的是一个class文件。该class文件经过反编译可以看到实际上是生成了一个类,该类继承了java.lang.Enum<E>。
我们可以把 enum 看成是一个普通的 class,它们都可以定义一些属性和方法,不同之处是:enum 不能使用 extends 关键字继承其他类,因为 enum 已经继承了 java.lang.Enum(java是单一继承)。
枚举被设计成单例模式,即枚举类型会由JVM在加载的时候实例化枚举对象,你在枚举类中定义了多少个就会实例化多少个,JVM为了保证每一个枚举类元素的唯一实例,不允许外部进行实例化,保证了单例对象的唯一性。
目前最安全的枚举单例模式(推荐)
public class EnumSingleton{
private EnumSingleton(){}
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
private static enum Singleton{
INSTANCE;
private EnumSingleton singleton;
//JVM会保证此方法绝对只调用一次
private Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return singleton;
}
}
}
目前最为安全的实现单例的方法是通过内部静态enum的方法来实现,因为JVM会保证enum不能被反射并且构造器方法只执行一次。