定义
一个类只有一个实例,并提供全局访问点。
实现:1)私有构造函数,2)私有静态变量保存对象实例,3)公有静态函数返回静态变量
实现方法
饿汉式–线程安全
类加载时就会创建对象,保证对象的唯一性。如果对象不被使用,会造成资源浪费。
public class Singleton {
private Singleton() {}
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
懒汉式–线程不安全
延迟加载,多线程环境下不能保证对象的唯一性。
public class Singleton {
private Singleton() {}
private static Singleton instance = null;
public static Singleton getInstance() {
if(instance == null) instance = new Singleton();
return instance;
}
}
懒汉式–线程安全
对getInstance加锁,锁上了整个类,同一时间点只能有一个线程进入方法,易造成线程阻塞严重。
public static synchronized Singleton getInstance() {...}
双重检查–禁止指令重排
双重校验锁先判断instance是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
public class Singleton {
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) instance = new Singleton();
}
}
return instance;
}
}
将instance设为volatile的目的是禁止指令重排。
在执行instance = new Singleton()语句时,可分为三步:
- 给对象分配内存空间
- 初始化对象
- 设置instance指向刚分配的内存地址。
如果不禁止指令重排,步骤2,3可能会颠倒,对于线程A,假如执行完了1,3,但没有完成对象初始化,此时线程B进入,判断对象不为null即返回,会发生异常。
允许重排–使指令重排不可见
采用静态内部类。
public class Singleton {
private Singleton(){}
private static class InnerClass {
private static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return InnerClass.instance;
}
}
类A初始化条件:
A类实例被创建
A类中声明的静态方法被调用
A类中声明的一个静态成员被赋值
A类中声明的一个静态成员被使用,并且这个成员不是常量成员
如果A类为顶级类,并且A类中有嵌套的断言语句(不常用)
上述任何一种情况首次发生,这个类就会立即被初始化。InnerClass.instance是调用类的静态成员,会使内部类初始化。类初始化时会有一把初始化锁,其他线程不可见。
破坏单例–序列化与反序列化
public static void main(String[] args) throws IOException, FileNotFoundException, ClassNotFoundException {
Singleton instance = Singleton.getInstance();
ObjectOutputStream oStream = new ObjectOutputStream(new FileOutputStream("singleton"));
oStream.writeObject(instance);
File file = new File("singleton");
ObjectInputStream iStream = new ObjectInputStream(new FileInputStream(file));
Singleton instance2 = (Singleton) iStream.readObject();
System.out.println(instance);
System.out.println(instance2);
}
运行结果:
注意:写测试的时候注意Singleton类要实现Serializable接口。
在ObjectInputStream类中,会判断该类是否实现了Serializable接口,如果是,通过反射构建对象,创建的是新的对象实例。
解决办法:
在Singleton类中添加方法:
private Object readResolve() {
return instance;
}
反射生成新的对象obj后,会执行hasReadResolveMethod方法,如果类实现了序列化,且有readResolve()方法,返回true,并执行invokeReadResolve(obj),即通过反射调用Singleton类中的readResolve()方法,获取了原来的对象实例。
破坏单例–反射攻击
通过反射可以获得类的私有构造器,利用该构造器可以构建新的类的对象,破坏了单例。
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = Singleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance = Singleton.getInstance();
Singleton newInstance = (Singleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
}
防御:
饿汉模式:
private Singleton() {
if(instance != null) throw new RuntimeException("禁止重复构建对象")
}
内部类模式:
private Singleton() {
if(InnerClass.instance != null) {
throw new RuntimeException("禁止重复创建对象");
}
}
枚举单例模式
可解决序列化反序列化及反射攻击问题。
public enum Singleton {
INSTANCE;
private Object objName;
public Object getObjName() {
return objName;
}
public void setObjName(Object objName) {
this.objName = objName;
}
public static Singleton getInstance() {
return INSTANCE;
}
}
对于枚举类型,是根据枚举的名字生成对象的,反序列化回来不变。
且枚举对象不能通过反射创建。