龙空技术网

设计模式实战-单例模式

瀚海求知 16

前言:

此刻兄弟们对“设计模式综合项目实战”都比较注意,我们都想要知道一些“设计模式综合项目实战”的相关资讯。那么小编在网上收集了一些有关“设计模式综合项目实战””的相关文章,希望咱们能喜欢,朋友们一起来学习一下吧!

其实一开始工作的时候,作为一个菜鸡程序员,在日常的膜拜大佬的过程中,往往会听到大佬们说过什么设计模式啊,什么什么原则啊,然后在自己的工作中也想要学习学习,马克一下,但是在日常的curd中,基本上是没有多少能够用到设计模式的地步,所以一直对于这个概念比较抽象,但是随着时间的变化,总感觉自己在开发上面有很些不对劲了,感觉自己写的东西说不出来的烂,于是拿起了我曾经抛下的设计模式,准备和自己的日常代码结合下,于是写个日志,记录下自己学习的过程。

前言:因为自己真算不上什么大佬,而且我自己看的其实也不能算太多,所以以下内容可能会有些想当然的内容,如果有什么问题希望各位大佬能帮忙指出,谢谢。

单例模式

先贴上菜鸟教程的介绍:单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

说白了就是单例模式只允许一个类存在一个实例,当然了,这个概念比较简单,但是在实践中,其实我们往往会不知道具体的使用方式,所以再正式使用这个单例模式之前,我们先考虑下一个问题,再哪里需要使用单例模式呢?很显然,使用这个单例模式就是为了减少重复的创建和销毁一个类的对象,那么反向考虑下,什么类不需要重复的创建和销毁呢?结论只有一个,那就是:全局使用的类。当然了,这个具体什么是全局使用的类,就要看各自的业务逻辑,以及相关的需求了,比方说项目中可能存在某些全局配置的类啊,某些用于记录日志,有关于临界资源这些需要进行全局统一管理的类啊,往往在使用这些全局的类,我们可能就要考虑使用单例模式进行相应的配置了。这边介绍几个应用上的例子,如:在web中我们可能会存在多个地方处理同一文件,对于一些web项目中需要的计数功能,需要生成一些需要唯一编号,并且编号是按照一定规则生成的。这些地方都可以使用单例模式。

单例的实现方式

单例模式的实现算的上是多种多样了,当然各种方案也有着自己不同的优缺点,这里先简单介绍下。

饿汉式

非常简单的一个实现单例方式。主要借助了java中classloader机制实现了多线程中的单例,但是因为在类加载的时候就初始化了对象,所以对于内存有所浪费,并且如果长期不使用这个单例的情况下,这个方式也会产生垃圾对象。

public class Singleton {      private static Singleton instance = new Singleton();      private Singleton (){}      public static Singleton getInstance() {      return instance;      }  }
懒汉式

最基本的实现单例方式,简单,但是有很大的问题,不支持多线程,因此我个人感觉这个严格上来说不能算是单例了,毕竟满容易出现多个实例的。当然因为是最基本的方案,可以在之后的方案在懒汉式上做了很多的优化。

public class Singleton {      private static Singleton instance;      private Singleton (){}         public static Singleton getInstance() {      // 主要就是这里如果判断初始化的时候存在问题,容易出现多个实例    if (instance == null) {          instance = new Singleton();      }      return instance;      }  }
懒汉式(线程安全模式)

懒汉式的升级版,说白了就是直接把懒汉式给加上了同步,用于解决线程不安全的情况。但是,加上了一个同步锁,基本上把获取单例对象的方法性能给延迟了,基本上等同把多线程改成了串行执行的方案。

public class Singleton {      private static Singleton instance;      private Singleton (){}      public static synchronized Singleton getInstance() {      if (instance == null) {          instance = new Singleton();      }      return instance;      }  }
双检锁

这个方案主要是针对于懒汉式(线程安全模式)的一个升级,对于整个方法加锁,很明显会很影响性能,于是我们在判断是否存在单例之后,在加锁,然后再在加锁的代码块内部加上一个判空,再来进行初始化的工作。这个方案明显性能上比懒汉式(线程安全模式)好的,但是因为jvm的指令重排机制,所以可能会出现空指针的异常。

public class Singleton {      private static Singleton instance;      private Singleton (){}      public static Singleton getInstance() {      if (instance == null) {          // 将同步锁放在了这里        synchronized (Singleton.class){               if(null==instance)                    instance=new Singleton();            }     }      return instance;      }  }
双检锁(volatile模式)

主要是针对于jdk1.5之后的volatile关键字的,这个关键字的具体内容和jvm内存模型有关,这里就不详细说了,单纯的说就是他能解决双检锁的指令重排问题,其他的和双检锁方案差不多。

public class Singleton {      private volatile static Singleton singleton;      private Singleton (){}      public static Singleton getSingleton() {      if (singleton == null) {          synchronized (Singleton.class) {          if (singleton == null) {              singleton = new Singleton();          }          }      }      return singleton;      }  }
静态内部类

这种方式主要是使用了静态内部类的延迟加载初始化方案。与饿汉式一样利用了 classloader 机制来保证初始化 instance 时只有一个线程这方式来控制同步,不过不一样的地方就是静态内部类不会和外部类一起被加载,只有当调用了外部类的getSingleton()方法时,才会装载内部类,从而延迟生成了单例。

public class Singleton {      private static class SingletonHolder {      private static final Singleton INSTANCE = new Singleton();      }      private Singleton (){}      public static final Singleton getInstance() {      return SingletonHolder.INSTANCE;      }  }
枚举类

个人觉得最简洁的实现单例的一个方案,并且这个方案解决了通过反射和反序列化生成多个实例的方式。主要用到的就是jdk1.5提供的枚举类来实现单例。

public enum Singleton {      INSTANCE;      public void whateverMethod() {      }  }

以上就是单例模式的7种实现方案了,具体的使用还是需要看各自的业务情况进行使用,毕竟我们才是决定代码的那个人啊。

附录反射和反序列化破解单例的方案(来源网上)

反射破解

public class BreakSingleton{    public static void main(String[] args) throw Exception{        Class clazz = Class.forName("Singleton");        Constructor c = clazz.getDeclaredConstructor(null);        c.setAccessible(true);        Singleton s1 = c.newInstance();        Singleton s2 = c.newInstance();        //通过反射,得到的两个不同对象        System.out.println(s1);        System.out.println(s2);    }}

如何避免以上的漏洞:

class Singleton{    private static final Singleton singleton = new Singleton();         private Singleton() {        //在构造器中加个逻辑判断,多次调用抛出异常        if(instance!= null){            throw new RuntimeException()        }    }    public static Singleton getInstance(){        return singleton;    }}

反序列化

public class BreakSingleton{  public static void main(String[] args) throws Exception{     //先根据单例模式创建对象(单例模式所以s1,s2是一样的)     Singleton s1=Singleton.getInstance();     Singleton s2=Singleton.getInstance();//将s1写入本地某个路径     FileOutputStream fos=new FileOutputStream("本地某个路径下文件");     ObjectOutputStream oos=new ObjectOutputStream(fos);     oos.writeObject(s1);     oos.close();     fos.close();//从本地某个路径读取写入的对象     ObjectInputStream ois=new ObjectInputStream(new FileInputStream("和上面的本地参数路径相同"));    Singleton s3=(Singleton) ois.readObject();     System.out.println(s1);     System.out.println(s2);     System.out.println(s3);//s3是一个新对象} }

如何避免实现序列化单例模式的漏洞:

class Singleton implements Serializable{  private static final Singleton singleton = new Singleton();   private Singleton() {  }  public static Singleton getInstance(){         return singleton;  }//反序列化定义该方法,则不需要创建新对象  private Object readResolve() throws ObjectStreamException{    return singleton;  }}

标签: #设计模式综合项目实战