单例模式
单例和静态类的区别
单例模式和静态类都具有良好的访问性,它们之间有许多相似之处,例如,两者可以直接使用而无须创建对象,都可提交唯一实例,在一个非常高的高度上看起来它们都为是用于同样的任务。
区别:
- 静态类比单例具有更好的性能,因为静态方法在编译期绑定。
- override的能力,因Java中的静态方法是不可以覆盖的,这就导致其木有太多的灵活性,另一面,你可通过继承的方式覆盖单例类中定义的方法。
- 静态类很难模拟,因此难于单例测试,单例更容易模拟,因为也比静态类易于编写单元测试,不论神马单例期望神马,你都可以传递模拟对象,例如构造方法或方法参数。
- 如果你的需求中需要维护状态信息,则单例比静态类更适合,因为后者在维护状态信息方面是非常可怕的,并导致狡滑的bug。
- 如果是一个非常重的对象,单例可以懒加载,但是静态类没有这样的优势,并且非常热切的加载。
- 许多依赖注入的框架对单例都有良好的管理,例如Spring,使用它们非常容易。
线程不安全的单例
懒汉模式
懒汉模式是在程序调用的时候再去初始化实例,不会浪费资源但是存在线程安全问题。
/**
* 懒汉模式
*/
public class LazyMan {
private LazyMan() {
}
private static LazyMan instance = null;
public static LazyMan getInstance() {
if (instance == null) instance = new LazyMan();
return instance;
}
}
线程安全的单例
饿汉模式
饿汉模式是在类加载的时候把instance就初始化好,不存在运行时初始化实例的问题,所以是线程安全的。
饿汉模式的缺点在于,如果这个单例没有被使用过,也会在内存中初始化好,所以可能会造成资源的浪费。
/**
* 饿汉模式
*/
public class HungerMan {
private static HungerMan instance = new HungerMan();
private HungerMan() {
}
public static HungerMan getInstance() {
return instance;
}
}
静态内部类
静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
detail:https://blog.csdn.net/mnb65482/article/details/80458571
/**
* 静态内部类
*/
public class InnerClassSingle {
private InnerClassSingle() {
}
public InnerClassSingle getInstance() {
return InnerClass.instance;
}
public static class InnerClass {
private static final InnerClassSingle instance = new InnerClassSingle();
}
}
DCL
双重检测的单例一定要用volatile来修饰instance。
instance = new DlcSingleton();
这个步骤在jvm中分为三步。
1.在堆内存开辟内存空间。
2.在堆内存中实例化SingleTon里面的各个参数。
3.把对象指向堆内存空间。
由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE 已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。
枚举单例
反射破坏单例
通过反射可以破坏以上单例
Constructor<DlcSingleton> constructor = DlcSingleton.class.getDeclaredConstructor(null);
constructor.setAccessible(true);//通过setAccessible,将私有属性可见
DlcSingleton singleton = constructor.newInstance();
进入newInstance方法,可以看到:
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
所以枚举的单例可以放置反射的破坏。
public enum EnumSington {
Instance;
public void fun() {
System.out.println("function");
}
}