单例模式及破解方法+枚举类型创建单例模式

1. 单例模式

单例模式主要分为饿汉式和懒汉式。

例如:

1.1 饿汉式

饿汉式即开始的时候就new一个对象

public class Person() {
   private static final Person p = new Person();
   private Person() {}
   public static Person getInstance(){
       return p;
  }
}

1.1 饿汉式的特点

饿汉式的特点:

  • 饿汉式是先天的线程安全且写法简单

  • 立即加载,用空间换取时间,会浪费一定内存空间但可以忽略不计,推荐使用

1.2 懒汉式

相对于饿汉式的一开始就创建对象,懒汉式则是在用到时才new对象出来。

例如:

public class Person(){
   private static Person p;
   private Person() {}
   public static Person getInstance(){
       if(p == null){
           p = new Person();
      }
       return p;
  }
}

1.2.1 懒汉式的特点

懒汉式的特点:

  • 懒汉式是线程不安全的模式,但可以通过后天实现线程安全

  • 延时加载,用时间换取空间,节省空间

1.2.2 懒汉式(低效)线程安全版

对于上面的懒汉式,存在线程不安全的情况,所以我们可以通过加锁的方式让其变为线程安全。

public class PersonSave {
   private static PersonSave p;
   private PersonSave() {}
   public static PersonSave getInstance() {
       // 加锁后 性能会低
       synchronized (PersonSave.class){
           if (p == null) {
               p = new PersonSave();
          }
      }
       return p;
  }
}

加锁后会使性能有所降低

1.2.3 懒汉式(高效)线程安全版

我们可以发现加锁后在多线程的情况下,所有线程都会停在synchronized前面进行判断,而实际运行中除第一个需要进去之外其余的无需进入该锁,所以我们可以在上面加一个判断从而使程序变得高效起来。

public class PersonSave {
   private static PersonSave p;
   private PersonSave() {}
   public static PersonSave getInstance() {
       if (p == null){
           synchronized (PersonSave.class){
               if (p == null) {
                   p = new PersonSave();
              }
          }
      }
       return p;
  }
}

2. 单例模式的破解

虽然单例模式旨在只创建一个对象,但是我们可以通过反射对单例模式进破坏。

2.1 通过构造器破解

2.1.1 通过构造器对单例模式的破解

public class PersonSave {
   private static PersonSave p;
   private PersonSave() {}
   public static PersonSave getInstance() {
       if (p == null){
           synchronized (PersonSave.class){
               if (p == null) {
                   p = new PersonSave();
              }
          }
      }
       return p;
  }
}
public class Test {
   public static void main(String[] args) throws Exception{
       Class<Person> c = Person.class;
       Constructor<Person> d = c.getDeclaredConstructor();
       d.setAccessible(true);
       Person p1 = d.newInstance();
       Person p2 = d.newInstance();
       System.out.println(p1 == p2);
  }
}

其输出结果为false;

2.1.2 改进手段

我们可以在定义一个值标记我们是否已经创建对象

public class Person{
   private static boolean bz = false;
   private static volatile Person p;

   private Person() {
       if (!bz) {
           bz = true;
      } else {
           throw new RuntimeException("不要试图用反射破环单例");
      }
  }

   public static Person getInstance() {
       if (p == null) {
           synchronized (Person.class) {
               if (p == null) {
                   p = new Person();
              }
          }
      }
       return p;
  }
}

2.2 再破解

我们之前设置了一个布朗值去判断是否已经创建对象,同理我们可以通过反射对值进行修改进一步对单例模式进行破解。

public static void main(String[] args) throws Exception {
       Field bz = Person.class.getDeclaredField("bz");
       bz.setAccessible(true);
       Constructor<Person> dc = Person.class.getDeclaredConstructor(null);
       dc.setAccessible(true);
       Person p1 = dc.newInstance();
       bz.set(p1, false);
       Person p2 = dc.newInstance();
       System.out.println(p1 == p2);
  }

3 枚举类型单例模式

如果我们用反射对枚举类型进行破解,则会在控制台输出Cannot reflectively create enum objects,即无法以反射方式创建枚举对象,所以我们用枚举类型创建单例模式可以防止对单例模式的破解

class Rourse{
   // 代码。。。
}

public enum LazyMAN {
   INSTANCE;
   private static Rourse rourse;
   LazyMAN(){}
   public static Rourse getInstance(){
       if (rourse == null){
           synchronized (LazyMAN.class){
               if (rourse == null){
                   rourse = new Rourse();
              }
          }
      }
       return rourse;
  }
}

class test{
   public static void main(String[] args) throws Exception{
       Constructor<LazyMAN> dc = LazyMAN.class.getDeclaredConstructor(String.class,int.class);
       dc.setAccessible(true);
       LazyMAN l1 = dc.newInstance();
       LazyMAN l2 = dc.newInstance();
       System.out.println(l1);
       System.out.println(l2);
  }
}

 

posted @ 2021-12-03 08:54  Black空我  阅读(107)  评论(0)    收藏  举报