单例模式是为了确保只有一个实例的存在,从而防止多个实例存在会对程序造成干扰或攻击。
1.懒汉模式
public class LazySingleTon {
private static LazySingleTon singleTon;
private LazySingleTon(){}
public synchronized static LazySingleTon getInstance(){
if(singleTon == null)
singleTon = new LazySingleTon();
return singleTon;
}
}
缺点:同步机制导致效率低下,并且无法防控反射攻击,如下
public class LazySingleTon {
private static LazySingleTon singleTon;
private LazySingleTon(){}
public synchronized static LazySingleTon getInstance(){
if(singleTon == null)
singleTon = new LazySingleTon();
else throw new RuntimeException("多个实例不允许");
return singleTon;
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazySingleTon> singleTonConstructor = LazySingleTon.class.getDeclaredConstructor();
singleTonConstructor.setAccessible(true);
LazySingleTon singleTon = singleTonConstructor.newInstance();
LazySingleTon singleTon1 = LazySingleTon.getInstance();
System.out.println(singleTon == singleTon1);
}
}
2.DCL机制 Double Check Locking
public class DCLSingleTon {
private volatile static DCLSingleTon singleTon;
private DCLSingleTon(){};
public static DCLSingleTon getInstance(){
if(singleTon == null){
synchronized (DCLSingleTon.class){
if(singleTon == null)
singleTon = new DCLSingleTon();
}
}
return singleTon;
}
}
DCL机制满足一般的并发情况,提高了效率,用volatile修饰singleTon是为了禁止指令重排序和优化,如singleTon = new DCLSingleTon();这一行不是由一步完成的,而是三个步骤完成的,包括堆分配空间、初始化,以及栈内引用指向堆空间地址三个步骤,而voliate保证了指令按照顺序进行,而不会被编译器优化,防止了空指针情况的出现。当然,DCL模式也无法防御反射攻击。
3.饿汉模式
public class SingTon {
public static SingTon singTon = new SingTon();
private SingTon(){
if(singTon != null) throw new RuntimeException("多个实例不允许");
}
public static SingTon getInstance(){
return singTon;
}
}
这种模式缺点是单例在最一开始就有了,而不是在需要它的时候才出现。好处是基于JVM类加载机制保证线程安全,并可以防护反射攻击
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<SingTon> constructor = SingTon.class.getDeclaredConstructor();
constructor.setAccessible(true);
SingTon singTon = constructor.newInstance();
SingTon singTon1 = SingTon.getInstance();
System.out.println(singTon == singTon1);
}
}
4.静态内部类
public class InnerSingleTon {
public static class InnerClassHoler{
private static InnerSingleTon singleTon = new InnerSingleTon();
}
private InnerSingleTon(){
if(InnerClassHoler.singleTon != null) throw new RuntimeException("多个实例不允许");
}
public static InnerSingleTon getInstance(){
return InnerClassHoler.singleTon;
}
}
优点可以防护反射攻击、同时只会被加载一次
改进:实现序列化
实现Serializable接口并定义long型序列号,写readResolve()方法
import java.io.Serializable;
public class InnerSingleTon implements Serializable {
static final long serialVersionUID = 42L;
public static class InnerClassHoler{
private static InnerSingleTon singleTon = new InnerSingleTon();
}
private InnerSingleTon(){
if(InnerClassHoler.singleTon != null) throw new RuntimeException("多个实例不允许");
}
public static InnerSingleTon getInstance(){
return InnerClassHoler.singleTon;
}
Object readResolve() {
return InnerClassHoler.singleTon;
}
final int a = 1;
}
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
InnerSingleTon singTon1 = InnerSingleTon.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("inner"));
oos.writeObject(singTon1);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("inner"));
InnerSingleTon obj = (InnerSingleTon) ois.readObject();
System.out.println(singTon1 == obj);
}
}
5.枚举
public enum EnumSingleTon {
INSTANCE;
}
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingleTon singleTon = EnumSingleTon.INSTANCE;
EnumSingleTon singleTon1 = EnumSingleTon.INSTANCE;
System.out.println(singleTon == singleTon1);
Constructor<EnumSingleTon> constructor = EnumSingleTon.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
EnumSingleTon singleTon2 = constructor.newInstance("INSTANCE",0);
System.out.println(singleTon == singleTon2);
}
}
实验结果表明枚举不能通过反射的方式创建实例,枚举类继承抽象类java.lang.Enum,所以在构造器的时候需要传参数
反射安全,反序列化机制安全 线程安全