亲宝软件园·资讯

展开

设计模式之单例模式

八门遁甲 人气:0

目录:

  • 什么是单例模式
  • 单例模式的应用场景
  • 单例模式的优缺点
  • 单例模式的实现
  • 总借

一、什么是单例模式

  单例模式顾名思义就是只存在一个实例,也就是系统代码中只需要一个对象的实例应用到全局代码中,有点类似全局变量。例如,在系统运行时,系统需要读取配置文件中的参数,在设计系统的时候读取配置文件的类往往设计成单例类。因为系统从启动运行到结束,只需要读取一次配置文件,这个读取配置文件全部由该类负责读取,在全局代码中只需要使用该类即可。这样不仅简化了配置文件的管理,也避免了代码读取配置文件数据的不一致性。

 

 单例模式的特点:

  1、该类的构造方法声明为private,这样其他类无法初始花该类,只能通过该类的public方法获取该类的对象。

  2、里面有个私有的对象成员,该成员对象是类本身,用于public方法返回该类的实例。

  3、该类中提供一个public的静态方法,返回该类的私有成员对象。

 

二、单例的应用场景

 

  举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。

 

  继续说回收站,我们在实际使用中并不存在需要同时打开两个回收站窗口的必要性。假如我每次创建回收站时都需要消耗大量的资源,而每个回收站之间资源是共享的,那么在没有必要多次重复创建该实例的情况下,创建了多个实例,这样做就会给系统造成不必要的负担,造成资源浪费。

 

  再举一个例子,网站的计数器,一般也是采用单例模式实现,如果你存在多个计数器,每一个用户的访问都刷新计数器的值,这样的话你的实计数的值是难以同步的。但是如果采用单例模式实现就不会存在这样的问题,而且还可以避免线程安全问题。同样多线程的线程池的设计一般也是采用单例模式,这是由于线程池需要方便对池中的线程进行控制

 

  同样,对于一些应用程序的日志应用,或者web开发中读取配置文件都适合使用单例模式,如HttpApplication 就是单例的典型应用。

 

  从上述的例子中我们可以总结出适合使用单例模式的场景和优缺点:  

 

   适用场景:

  1.需要生成唯一序列的环境

  2.需要频繁实例化然后销毁的对象。

  3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。 

  4.方便资源相互通信的环境

 

 

三、单例模式的优缺点

  优点:

    1、在内存中只有一个对象,节省内存空间;

    2、避免频繁的创建销毁对象,可以提高性能;

    3、避免对共享资源的多重占用,简化访问;

    4、为整个系统提供一个全局访问点。

  缺点:

    1、不适用于变化频繁的对象;

    2、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;

    3、如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;

 

四、单例模式的实现

  1、饿汉式

public class Mgr{
    //创建自己的实例,并初始化私有静态final成员
    private static final Mgr mgr = new Mgr();
    //私有构造方法
    private Mgr() {}; 
    //公共方法,返回自己的实例化成员
    public static Mgr getMgr() { 
        return  mgr;
    }
}

  备注:该单例实现方法简单明了,推荐使用。该类被JVM加到内存的时候,只会加载一次,并且只实例化一个单例,优点是具有线程安全性,缺点是:不用他也在内存中实例化,浪费内存。所以提出了懒散式实现方式。

  2、懒汉式

public class Mgr{
   //声明私有静态对象成员,作为返回值
    private static Mgr mgr;
   //私有构造函数
    private Mgr() {}; 
   //懒汉的特点,提供公共静态方法,如果该成员对象为空,实例化并返回
    public static Mgr getMgr() {
        if(mgr == null){
            mgr =  = new Mgr();
        }
         return  mgr;
     }
}

  备注:(不推荐用)这种实现方法只有程序在调用该类的getMgr方法才实例话对象并返回,特点就是调用的时候再实例化并返回,延迟加载的被动形式。但是该实现方法不是线程安全的,因为当同时有有两个线程执行到if(mgr==null)语句的时候,由于某些原因其中一个线程先一步执行下一句,实例化了对象并返回;两一个线程再实例化对象在返回,这两个线程返回的对象不是同一个对象(这难道还是单例吗!),所以该实现方法的缺点也很明显。那为了避免线程不安全问题,在懒汉写法上提出加锁的实现方式。

  3、给公共方法加锁

public class Mgr{
    //声明私有静态对象成员,作为返回值
    private static Mgr mgr;
    //私有构造函数
    private Mgr() {}; 
    //给公共方法加锁,只有一个线程第一次获得锁实例化对象并返回
    public static syncnronized Mgr getMgr() {
        if(mgr == null){
            mgr = new Mgr();
        }
        return  mgr;
   }
}

  备注:(推荐使用)这种实现方式比较完善,既保证了懒散式的延迟加载方式,也保证了线程安全。缺点是在整个方法上加锁,导致性能下降。因为只有第一次获得锁的线程实例化对象并返回,以后的线程获得锁的时候执行 if(mgr == null)语句的时候,由于mgr已经实例化了不为空,直接跳过返回实例。整个过程要竞争锁,不能并发执行导致性能下降。那为优化性能下降问题,那我只在mgr = new Mgr()上加锁,保证锁粒度最小化的同时保证单例实例化。

  4、给实例化加锁

public class Mgr{
    //私有静态成员对象
    private static Mgr mgr;
    //私有构造函数
    private Mgr() {}; 
    //公共方法,在实例化语句块加锁,保证单例
    public static  Mgr getMgr() {
        if(mgr == null){
            syncnronized(Mgr.class){
                mgr = new Mgr();
            }
        }
         return  mgr;
  }
}

  备注:(不推荐使用)该实现方法虽然相较方法3性能有所提升,但并不能保证线程安全。因为当两个线程同时执行if(mgr == null)语句时,其中线程1获取锁,实例化对象并返回,线程2在获得锁又在实例化对象并返回。线程1和线程2获取的对象并不是同一个。所以在此基础上提出了双重判断方式。

5、双重判断加锁

public class Mgr{
    //私有静态成员对象
    private static  Mgr mgr;
    //私有构造函数
    private Mgr() {}; 
    //公共方法提供双重判断并在实例化代码块加锁
    public static  Mgr getMgr() {
        if(mgr == null){ //第一次判断
            syncnronized(Mgr.class){
                if(mgr == null){ //第二次判断
                    mgr =  = new Mgr();
                }      
            }
        }
         return  mgr;
  }
}

  备注:(推荐使用)相较于方法4,该方法双重判定,如果多线程同时执行到第一次判断语句位置,其中一个线程获得锁,由于是第一次该对象成员为空,实例化后并返回。其后其它线程调用公共方法的时候,由于实例化了,在第一次判断自接返回实例,不在产生锁竞争。大大提高了效率,保证了线程的安全性,也保证了延迟加载的特性。

 6、静态内部类

public class Mgr{
    private Mgr() {};
    //定义静态内部类
    private static class MgrHolder{
        private final static Mgr mgr = new Mgr();
    } 
    //公共方法直接返回静态内部类的实例对象
    public static  Mgr getMgr() {
        return  MgrHolder.mgr;
  }
}

  备注:(可使用)该实现方法通过JVM来保证线程安全性,静态内部类MgrHolder来New一个Mgr对象,JVM只会加载一次Mgr类(静态内部类不会加载),当类调用getMgr方法的时候,也只会调用一次,公共方法调用静态内部类,获取一个对象(也是实现懒加载)。所以也是线程安全的。

7、枚举类单例模式

public enum Mgr{
    mgr;
    public void m(){} //业务方法
}

  备注:(推荐使用)jdk1.5之后才能正常达到单例效果,参考来自《Effective Java》。注意枚举类的枚举变量必须写在第一行,后面实现业务代码。调用方式是:Mgr.mgr.Function_Name();具备枚举类型的特点,有点是:线程同步,防止反序列化。

五、总结

  通过上面几种单例模式的实现方式的列举,但是在实际应用中其中的2,3,4三种方式并不适用,列出来只是让读者更好的理解方式5的由来,起到抛砖引玉的作用,更好的理解单例模式。总之常用的四种,懒汉,双重校验锁,静态内部类,枚举单例。

  饿汉:类加载的时候就创建实例,所以是线程安全的,但不能延迟加载。

  双重校验锁:线程安全,效率较高,延迟加载。

  静态内部类:实现起来比较麻烦,在不同的编译器上会出现不可预知的错误。

  枚举单例:很好,不仅避免了多线程同步问题,而且能反正反序列化重新创建对象,但是不能延迟加载,用的人少。

 

  • 读者发现有什么有问题的地方谢谢留言指正。部分参考自:https://www.cnblogs.com/xuwendong/p/9633985.html#_label0

 

 

加载全部内容

相关教程
猜你喜欢
用户评论