单例模式
单例模式定义:不管在什么场景下只能生成一个类实例;
单例模式主要注意以下几个问题:
1. 线程安全问题;解决方法:sychronized、静态内部类、双重检测时用volatile;
2. 反射破坏单例模式;解决方法:单例构造函数中加对象非空校验;
3. 序列化反序列化破坏单例模式;解决方法:重写readResolve方法或者用枚举单例;
懒汉和饿汉的本质区别,就是实例化对象的时机,即是什么时候将对象创建起来
饿汉式:在加载类的时候就创建单例对象,所以调用时肯定线程安全
缺点是:不管用不用,都会创建对象,消耗内存对象,可能造成浪费(占着茅坑不拉屎)
@ThreadSafe
public class Hungry {
private Hungry() {}
private static final Hungry instance = new Hungry();
public static Hungry getInstance() { return instance; }
}
懒汉式单例模式建议使用静态内部类,JVM能够确保线程安全问题,但是要解决反射攻击和序列化反序列化攻击,还得通过代码来实现:
解决反射攻击的问题:需要在构造函数中控制私有静态成员变量的重复创建;
解决序列化反序列化攻击的问题:重写readResolve()方法
/**
* Created by marcopan on 2018/9/14.
* 有反射攻击和序列化攻击的问题
*
* 反射攻击解决方法,在构造函数中判断单例是否为空
*/
public class LazySingleton implements Serializable {
private LazySingleton() {
// 防止通过反射来破坏单例
if (LazySingletonHolder.singleton != null) {
throw new RuntimeException("不允许创建多个实例");
}
}
public static final LazySingleton getInstance() {
return LazySingletonHolder.singleton;
}
private static class LazySingletonHolder {
private static final LazySingleton singleton = new LazySingleton();
}
// public static void main(String[] args) {
// try {
// //很无聊的情况下,进行破坏
// Class<?> clazz = LazySingleton.class;
// //通过反射拿到私有的构造方法
// Constructor c = clazz.getDeclaredConstructor(null);
// //强制访问,强吻,不愿意也要吻
// c.setAccessible(true);
//
// //暴力初始化
// Object o1 = c.newInstance();
// //调用了两次构造方法,相当于new了两次,犯了原则性问题,
// Object o2 = LazySingleton.getInstance();//c.newInstance();
//
// System.out.println(o1 == o2);
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
private Object readResolve() {
return LazySingletonHolder.singleton;
}
public static void main(String[] args) {
try {
LazySingleton instance1 = LazySingleton.getInstance();
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
LazySingleton instance2 = (LazySingleton) ois.readObject();
ois.close();
System.out.println(instance1 == instance2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
双重校验机制需要添加volatile关键字,防止指令重排序,双重校验机制同样无法解决反射攻击和序列化反序列化的问题。
public class RegisterSingleton {
/** volatile防止指令重排序
* 有反射和序列化攻击的问题
*/
private volatile static RegisterSingleton lazy = null;
private RegisterSingleton() {
}
public static RegisterSingleton getInstance() {
if (lazy == null) {
synchronized (RegisterSingleton.class) {
if (lazy == null) {
lazy = new RegisterSingleton();
//1.分配内存给这个对象
//2.初始化对象
//3.设置lazy指向刚分配的内存地址
//4.初次访问对象
}
}
}
return lazy;
}
}
枚举单例模式能够完美解决反射攻击和序列化反序列化的问题。
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
try {
EnumSingleton instance1 = EnumSingleton.getInstance();
instance1.setData(new Object());
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance1);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
EnumSingleton instance2 = (EnumSingleton) ois.readObject();
ois.close();
System.out.println(instance1.getData());
System.out.println(instance2.getData());
System.out.println(instance1.getData() == instance2.getData());
} catch (Exception e) {
e.printStackTrace();
}
}
}
其中关于枚举的可以参考这篇文章,写得挺透彻的http://www.manongjc.com/article/1597.html
这里还要强调一点关于双重检查锁的情况,双重检查锁之所以是线程不安全的,原因在于CPU处理器会对指令进行重排序,有线程可能会拿到没有完成初始化的对象。双重检查锁要使用volatile变量避免双重检查锁线程不安全的问题

浙公网安备 33010602011771号