饿汉式单例
可能造成空间的浪费
// 饿汉式单例
public class Hungry {
// 私有构造器
private Hungry(){
}
// 先实例化对象
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式单例
// 懒汉式单例
public class Lazy {
// 私有构造器
private Lazy(){
}
// 先不实例化
private static Lazy LAZY;
public static Lazy getInstance(){
// 先判断再实例化
if (LAZY == null) {
LAZY = new Lazy();
}
return LAZY;
}
}
以上这种单例在单线程的情况下是可以的,但是如果是多线程的情况单例模式就会被破坏,所以我们要加锁如下
// 懒汉式单例
public class Lazy {
// 私有构造器
private Lazy(){
}
// 先不实例化
private static Lazy LAZY;
public static Lazy getInstance(){
// 一重判断
if (LAZY == null) {
// 加锁
synchronized (Lazy.class){
// 二重判断后再实例化
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
}
以上这样做确实可以在多线程的情况下防止单例被破坏,但是在极端的情况下,我们的实例化操作LAZY = new Lazy();
不是一个原子性的操作。实例化的过程是先分配空间,通过构造器实例化对象,再将对象指向地址空间。这三步操作的指令顺序是有可能改变的,如果是先分配空间还没实例化对象就直接指向该地址空间,该空间此时是空的,如果这时候一条线程进来会判断LAZY != null
直接返回对象,造成问题。所以我们还要加上volatile
来保证有序性指令不会重排序。
// DCL懒汉式单例
public class Lazy {
// 私有构造器
private Lazy(){
}
// 加上volatile
private volatile static Lazy LAZY;
public static Lazy getInstance(){
// 一重判断
if (LAZY == null) {
// 加锁
synchronized (Lazy.class){
// 二重判断后再实例化
if (LAZY == null) {
LAZY = new Lazy();
}
}
}
return LAZY;
}
}
虽然线程问题搞定了,但是通过反射我们还是可以破坏掉单例模式
public static void main(String[] args) throws Exception {
// 用单例模式获取对象
Lazy lazy1 = Lazy.getInstance();
// 通过反射获取无参构造
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
// 将无参构造私有破坏掉
declaredConstructor.setAccessible(true);
// 通过反射实例化对象
Lazy lazy2 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
输出的结果不是同一个
我们可以通过再无参构造里面进行判断抛出异常来解决,但是如果两个对象都是通过反射实例化,我们则需要加一个私有变量进行判断
// 可以用加密算法生成变量
private static Boolean askjdhsda = false;
// 私有构造器
private Lazy(){
synchronized (Lazy.class){
if (askjdhsda == false){
askjdhsda = true;
} else {
throw new RuntimeException();
}
}
}
这样做可以防止单例被破坏,但是还是有可能通过反编译和解密发现这个变量,再通过反射将我们新定义的变量值改变,通过newInstance()
源码
public T newInstance(Object... var1) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (!this.override && !Reflection.quickCheckMemberAccess(this.clazz, this.modifiers)) {
Class var2 = Reflection.getCallerClass();
this.checkAccess(var2, this.clazz, (Object)null, this.modifiers);
}
if ((this.clazz.getModifiers() & 16384) != 0) {
throw new IllegalArgumentException("Cannot reflectively create enum objects");
} else {
ConstructorAccessor var4 = this.constructorAccessor;
if (var4 == null) {
var4 = this.acquireConstructorAccessor();
}
Object var3 = var4.newInstance(var1);
return var3;
}
}
我们发现使用枚举可以防止反射破坏单例,下面是一个验证
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingleton enumSingleton1 = declaredConstructor.newInstance();
EnumSingleton enumSingleton2 = declaredConstructor.newInstance();
System.out.println(enumSingleton1);
System.out.println(enumSingleton2);
}
}
得到结果抛出了一个异常Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
所以使用枚举,反射也不能破坏单例模式