实际开发中,我们经常遇到某些类是全局只有唯一一个实例,一方面是实际的业务场景;另一方面也是为了节约系统资源。这个唯一实例创建成功之后,我们无法再创建一个同类型的其它对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性,我们可以通过单例模式来实现。
单例模式又可以分为几种实现方式:
顾名思义,饿汉一样狼吞虎咽,直接在静态属性上实例化
public class Singleton {
//饿汉式
private static final Singleton INSTANCE = new Singleton();
//必须添加private,让外界无法直接实例化单例类
private Singleton(){}
public static Singleton getInstance(){
return INSTANCE;
}
}
当类被加载时,静态变量INSTANCE会被初始化,此时类的私有构造函数会被调用,单例类的唯
一实例将被创建。
优点:
由于饿汉式单例是靠JVM加载class实现的唯一一次实例化,不会出现创建多个单例对象的情况,可确保单例对象的唯一性,线程安全。
缺点:
由于是在类加载的时候就被实例化,本身也是一个缺点,不是在用到的时候创建,可能会出现提前创建的情况,而且类加载时间可能会比较长。
顾名思义,懒汉肯定是很懒的,不到用的时候不会去创建实例的
public class Singleton {
//懒汉式
private static Singleton INSTANCE;
//必须添加private,让外界无法直接实例化单例类
private Singleton(){}
public static synchronized Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
上面的代码synchronized直接在整个方法上,其实已经够用了。但是如果你还想继续优化,可以减小同步块代码来进一步优化,最终如下:
public class Singleton {
//懒汉式
private static volatile Singleton INSTANCE;
//必须添加private,让外界无法直接实例化单例类
private Singleton(){}
//double check locking 双重检查锁定
public static Singleton getInstance(){
//一层check
if(INSTANCE == null){
synchronized (Singleton.class){
//二层check
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
上面代码可以看到,使用了双重检查锁定来实现懒汉式单例,如果只是使用第一层check,是线程不安全的,可能出现多个实例(比如:线程1和2进入if(INSTANCE == null)里,线程2等待线程1实例化后也会再次实例化一次),所以加了个二次check,防止线程2再次去实例化。使用双重检查锁定来实现懒汉式单例,需要在静态成员变量INSTANCE之前增加修饰符volatile(涉及到java实例化指令的最小执行单元,大概是从寄存器直接取值刷新,防止缓存值的误差),被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。
优点:
保证线程安全;且在使用时才去实例化。
缺点:
编码较繁琐;由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此使用双重检查锁定来实现单例模式也不是一种完美的实现方式。下面介绍一种更加好的实现方式。
顾名思义,在单例类内部定义静态内部类,在内部类里面的静态属性上实例化单例类
public class Singleton {
//必须添加private,让外界无法直接实例化单例类
private Singleton(){}
public static Singleton getInstance(){
return InstanceHolder.INSTANCE;
}
static class InstanceHolder{
private static final Singleton INSTANCE = new Singleton();
}
}
静态内部类实现单例,既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为
一种最好的Java语言单例模式实现方式。
以上所有的实现方式都可以通过反射和反序列化实现多个对象的创建,那么就引出了枚举实现单例。
public enum Singleton {
INSTANCE1;
Singleton(){}
public static Singleton getInstance(){
return INSTANCE1;
}
}
枚举实现单例为什么可以防止反射和反序列化实现多个对象的创建呢?
使用反射调用newInstance会报错
java.lang.InstantiationException
参考ObjectInputStream.java中的 Enum> readEnum(boolean unshared) throws IOException 方法,根据类名和枚举名称确定唯一的枚举实例,所以反序列化后还是原来的枚举对象
private Enum<?> readEnum(boolean unshared) throws IOException {
...
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
//这里只是根据类名和枚举名称确定唯一的枚举实例
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
...
return result;
}
当然使用枚举实现单例还可以方便实现多列。
实际项目中还是根据实际业务需求使用不同的实现方式,一般够用就可以了,比如一般情况下你使用静态内部类实现单例,这个就足够了。