单例模式 | 菜鸟教程 这个非常的详细,本文仅仅抽取觉得重要的点进行记录。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式分为两种:懒汉和饿汉式
饿汉式
描述:
- 线程安全
- 类加载时就初始化,浪费内存。
- 没有加锁,执行效率会提高。
- 容易产生垃圾对象。
实现
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}
注意
- 构造方法和属性都是private的
懒汉式
描述:
- 第一次调用才初始化,避免内存浪费。
- 线程不安全
- 必须加锁 synchronized 才能保证单例,但加锁会影响效率。
例子
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
DCL双重校验/懒汉式
疑问:为什么要上两重锁
我们来看看一重锁的时候
public class DclLazySingle {
private volatile static DclLazySingle lazySingle;
private DclLazySingle(){
}
public static DclLazySingle newInstance(){
synchronized (DclLazySingle.class){
if (lazySingle == null){
lazySingle = new DclLazySingle();
}
}
return lazySingle;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(DclLazySingle.newInstance());
}).start();
}
}
}
在new对象的时候会发生
- 类加载检查
- 分配内存
- 初始化零值
- 执行init方法
而且写入对象的期间线程是不安全的。有可能同时会排队去争夺锁,所以需要双重锁。而且为了防止指令重排,需要加入volatile。
这样做并不是完全的线程安全,单例模式可以被反射破坏
public class DclLazySingle {
private volatile static DclLazySingle lazySingle;
private DclLazySingle(){
}
public static DclLazySingle newInstances(){
synchronized (DclLazySingle.class){
if (lazySingle == null){
lazySingle = new DclLazySingle();
}
}
return lazySingle;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
DclLazySingle instance = DclLazySingle.newInstances();
//反射进行破坏
Constructor<DclLazySingle> declaredConstructor = DclLazySingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DclLazySingle instance2 = declaredConstructor.newInstance();
System.out.println(instance == instance2);
}
}
结果
false
那怎么解决呢?
在构造方法加入限制
public class DclLazySingle {
private volatile static DclLazySingle lazySingle;
private DclLazySingle(){
if (lazySingle != null){
System.out.println("想利用反射?没门");
throw new RuntimeException();
}
}
public static DclLazySingle newInstances(){
synchronized (DclLazySingle.class){
if (lazySingle == null){
lazySingle = new DclLazySingle();
}
}
return lazySingle;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
DclLazySingle instance = DclLazySingle.newInstances();
//反射进行破坏
Constructor<DclLazySingle> declaredConstructor = DclLazySingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DclLazySingle instance2 = declaredConstructor.newInstance();
System.out.println(instance == instance2);
}
}
结果
想利用反射?没门
Exception in thread "main" java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.pzy.singles.DclLazySingle.main(DclLazySingle.java:27)
Caused by: java.lang.RuntimeException
at com.pzy.singles.DclLazySingle.<init>(DclLazySingle.java:11)
... 5 more
Process finished with exit code 1
继续破坏
我两种方式都使用newInstance进行创建
public class DclLazySingle {
private volatile static DclLazySingle lazySingle;
private DclLazySingle(){
if (lazySingle != null){
System.out.println("想利用反射?没门");
throw new RuntimeException();
}
}
public static DclLazySingle newInstances(){
synchronized (DclLazySingle.class){
if (lazySingle == null){
lazySingle = new DclLazySingle();
}
}
return lazySingle;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//反射进行破坏
Constructor<DclLazySingle> declaredConstructor = DclLazySingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DclLazySingle instance1 = declaredConstructor.newInstance();
DclLazySingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
}
}
结果
false
解决:
加一个临时变量,初始默认为false,当构造器运行后变为true
public class DclLazySingle {
private static boolean flag = false;
private volatile static DclLazySingle lazySingle;
private DclLazySingle(){
if (!flag){
flag = true;
}
else {
throw new RuntimeException();
}
if (lazySingle != null){
System.out.println("想利用反射?没门");
throw new RuntimeException();
}
}
public static DclLazySingle newInstances(){
synchronized (DclLazySingle.class){
if (lazySingle == null){
lazySingle = new DclLazySingle();
}
}
return lazySingle;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//反射进行破坏
Constructor<DclLazySingle> declaredConstructor = DclLazySingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
DclLazySingle instance1 = declaredConstructor.newInstance();
DclLazySingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
}
}
继续破坏。。。。。。利用遍历或者flag名字进行修改,所以会一直无穷无尽出现问题
枚举
public enum EnumDemo {
INSTANCE;
public EnumDemo getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumDemo demo = EnumDemo.INSTANCE;
EnumDemo demo2 = EnumDemo.INSTANCE;
System.out.println(demo == demo2);
}
}
为什么枚举就不会被破坏呢?
我们看看newInstance代码
public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (!this.override) {
Class<?> caller = Reflection.getCallerClass();
this.checkAccess(caller, this.clazz, this.clazz, this.modifiers);
}
if ((this.clazz.getModifiers() & 16384) != 0) {
throw new IllegalArgumentException("Cannot reflectively create enum objects");
} else {
ConstructorAccessor ca = this.constructorAccessor;
if (ca == null) {
ca = this.acquireConstructorAccessor();
}
T inst = ca.newInstance(initargs);
return inst;
}
}
如果是枚举类型,就会抛出异常,因此只有枚举才能避免被反射破坏
反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败
所以在构建唯一的实例的时候,利用枚举进行构建是一个不错的选择。
参考: