单例模式
饿汉式单例模式
单例模式,就是自己先把自己创建了,整个程序都只有这一个实例,别人都没有办法创建实例,因为他的构造方法是private的
- 一次性把全部都创建了
java
public class HungryMan {
private static int [][] s = new int[5][5];
private static int [][] s1 = new int[5][5];
private static int [][] s2 = new int[5][5];
private static HungryMan hungryMan = new HungryMan();
private HungryMan() {
}
private static HungryMan getInstance() {
return hungryMan;
}
}
- 这样就会存在很多浪费空间
懒汉式单例模式
为了解决这种浪费,出现了懒汉式单例模式
-
就是我什么都不创建,需要使用的时候再去创建
java
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan() {
}
private static LazyMan getLazyMan(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
单例模式的问题
- 在单线程下,创建是没有问题的,但是在多线程下创建就会有问题
java
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan() {
System.out.println(Thread.currentThread().getName() );
}
private static LazyMan getLazyMan(){
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getLazyMan();
}).start();
}
}
}
-
这里注意几个点
-
需要在构造方法中输出
-
而不是在
javanew Thread(()->{ System.out.println(LazyMan.getLazyMan()); }).start();
- 因为sout是加锁的,所以拿到都是同一个对象
-
这样才能测试出结果
-
解决问题
- 双重检测锁【DCL懒汉式】
- 为什么需要二次判断
- 因为第一个判断是在同步代码块外的,所以很多线程都会去判断
- 如果没有第二个判断,可能存在有线程已经创建了实例,所以会创建出多个实例
- 当然你直接将整个代码块全部锁上也是可以的
- 这个代码也是不安全的
- 因为创建对象也不是原子性操作
- 分配空间
- 执行构造方法,初始化对象
- 把对象指向这个空间
- 所以也是不安全的,会出现重排,导致不安全
- 需要给对象加上volatile,防治重排
java
//双重检测锁,解决这个问题
private static LazyMan getLazyMan(){
if(lazyMan == null){
synchronized (LazyMan.class) {
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
-
双重检测锁的问题
javapublic static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //通过构建实例,构建对象 LazyMan lazyMan = LazyMan.getLazyMan(); //getClass拿到反射对象,通过反射对象获取到构造方法,因为是无参,所以是null Constructor<? extends LazyMan> constructor = lazyMan.getClass().getDeclaredConstructor(null); //将私有变量变为公有变量 constructor.setAccessible(true); //创建实例 LazyMan lazyMan1 = constructor.newInstance(); //或者 LazyMan lazyMan2 = new LazyMan(); System.out.println(lazyMan); System.out.println(lazyMan1); System.out.println(lazyMan2); }
- 通过反射将其破坏了
-
解决方式
- 创建对象的时候,添加判断
java
private LazyMan() {
synchronized (LazyMan.class){
if(lazyMan != null){
throw new RuntimeException("实例已经被创建");
}
}
}
- 出现新的问题
- 之前通过实例.getClass(),拿到反射对象,现在直接LazyMan.Class拿到对象,也不new对象了,直接newInstance(),获得到对象,这样单例模式还是被破坏了
java
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//通过构建实例,构建对象
//LazyMan lazyMan = LazyMan.getLazyMan();
//getClass拿到反射对象,通过反射对象获取到构造方法,因为是无参,所以是null
Constructor<? extends LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
//将私有变量变为公有变量
constructor.setAccessible(true);
//创建实例
LazyMan lazyMan1 = constructor.newInstance();
LazyMan lazyMan2 = constructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
}
-
枚举类
-
枚举类是没有不可以通过反射办法破环单例模式的
-
可以看出如果获取枚举类的instance是会抛出异常的
-
尝试破坏枚举类的单例模式
javapublic enum LazyEnum { INSTANCE; } class Test{ public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { LazyEnum instance = LazyEnum.INSTANCE; System.out.println(instance); Constructor<LazyEnum> declaredConstructor = LazyEnum.class.getDeclaredConstructor(String.class, int.class); declaredConstructor.setAccessible(true); LazyEnum lazyEnum = declaredConstructor.newInstance(); System.out.println(lazyEnum); } }
-
关于这个为什么是一个有参数的构造
-
如何知道枚举类是有参构造还是无参构造
-
这里显示了是一个无参构造
-
对于字节码文件的查看,显示的也是无参构造
-
-
如果是无参构造就会抛出NoSuchMethod的错误,表示没有这个方法
-
需要使用别的方法去查看到底是有参数还是无参构造
- 使用的软件是jad.exe
- 将LazyEnum.class文件反编译成Lazy.java文件,再进去查看,这样我们可以得到一个含有有参构造的构造函数,参数分别是Sring 和 int(注意不是Integer)
-