单例模式实现的五种方式:
public class HungrySingle {
//这里方法均为static,其他类可直接使用。
private static final HungrySingle single = new HungrySingle();
private HungrySingle(){}
//如果自己不创建构造方法,会自动创建无参public构造方法
public static HungrySingle getInstance(){
return single;
}
}
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。但没有懒加载(延时加载)效果,内存使用率低。
JDK 中的 Runtime 类就是使用饿汉模式实现的单例。
每次获取对象都需要加锁,释放锁,效率低。
public class LazySingle {
private static LazySingle single;
private LazySingle(){}
public static synchronized LazySingle getInstance(){
if(single == null){
single = new LazySingle();
}
return single;
}
}
下面是一种懒汉模式线程不安全的写法,多线程下可能创造出多个实例。
public class LazySingle {
private static LazySingle single;
private LazySingle(){}
public static LazySingle getInstance(){
if(single == null){
single = new LazySingle();
}
return single;
}
}
双重校验锁式 DCL (Double Check Lock
) ,JDK 1.5 以前使用不安全。
public class LazySingle {
private volatile static LazySingle single;
private LazySingle(){}
public static LazySingle getInstance(){
if(single == null){
synchronized (LazySingle.class) {
//对类对象加锁,所有对象均不能同时运行。
if(single == null){
single = new LazySingle();
}
}
}
return single;
}
}
详细请看 -> Java 懒汉模式之Volatile优化
这种方式能达到双检锁方式一样的功效,但实现更简单。这种方式利用了 类加载机制 来保证初始化实例时只有一个线程,从而保证线程安全。
这种方式当 LazySingle 类被装载了,静态内部类 SingleHolder 不会被初始化。因为 SingleHolder 类没有被主动使用,只有显式通过调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance,实现懒加载。
内部类都是延时加载的,也就是说只会在第一次使用时加载。不使用就不加载,所以可以很好的实现单例模式。
双亲委派模型保证类不会被加载多次。
public class LazySingle {
private static class SingleHolder{
private static final LazySingle instance = new LazySingle();
}
private LazySingle() {}
public static LazySingle getInstance() {
return SingleHolder.instance;
}
}
public enum Singleton {
INSTANCE;
private String objName;
public String getObjName() {
return objName;
}
public void setObjName(String objName) {
this.objName = objName;
}
public static void main(String[] args) {
// 单例测试
Singleton firstSingleton = Singleton.INSTANCE;
firstSingleton.setObjName("firstName");
System.out.println(firstSingleton.getObjName());
Singleton secondSingleton = Singleton.INSTANCE;
secondSingleton.setObjName("secondName");
System.out.println(firstSingleton.getObjName());
System.out.println(secondSingleton.getObjName());
// 反射获取实例测试
try {
Singleton[] enumConstants = Singleton.class.getEnumConstants();
for (Singleton enumConstant : enumConstants) {
System.out.println(enumConstant.getObjName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
firstName
secondName
secondName
secondName
该实现既能保证线程安全,又能防止反序列化重新创建新的对象。
该实现在多次序列化再进行反序列化之后,不会得到多个实例。而其它实现需要使用 transient 修饰所有字段,并且实现序列化和反序列化的方法。
该实现可以防止反射攻击。在其它实现中,通过 setAccessible() 方法可以将私有构造函数的访问级别设置为 public,然后调用构造函数从而实例化对象,如果要防止这种攻击,需要在构造函数中添加防止多次实例化的代码。该实现是由 JVM 保证只会实例化一次,因此不会出现上述的反射攻击。