设计模式之单例模式

目录:

  • 什么是单例模式

  • 单例模式的应用场景

  • 单例模式的优缺点

  • 单例模式的实现

  • 总借

一、什么是单例模式

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

单例模式的特点:

  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的由来,起到抛砖引玉的作用,更好的理解单例模式。总之常用的四种,懒汉,双重校验锁,静态内部类,枚举单例。

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

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

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

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

(0)

相关推荐

  • 23种设计模式入门 -- 单例模式

    单例模式:采用一定的方法,使得软件运行中,对于某个类只能存在一个实例对象,并且该类只能提供一个取得实例的方法. 分类: 饿汉式 静态常量方式 静态代码块方式 懒汉式 普通方式,线程不安全 同步方法方式 ...

  • java常见设计模式之---单例模式

    java常见设计模式之---单例模式 1.单例模式简介 应用场景举例 2.单例模式的特点 3.单例模式和静态类 4.单例模式的经典实现 饿汉式单例(典型实现) 饿汉式-静态代码块 懒汉式单例创建,五种 ...

  • 设计模式笔记(一):Singleton 设计模式

    今天开始学习设计模式,借此机会学习并整理学习笔记. 设计模式是一门不区分语言的课程,什么样的编程语言都可以用到设计模式.如果说java语法规则比作武功招式的话,那么设计模式就是心法. 设计模式共有23 ...

  • 设计模式:单例模式 (关于饿汉式和懒汉式)

    定义 单例模式是比较常见的一种设计模式,目的是保证一个类只能有一个实例,而且自行实例化并向整个系统提供这个实例,避免频繁创建对象,节约内存. 单例模式的应用场景很多, 比如我们电脑的操作系统的回收站就 ...

  • Java设计模式之单例模式

    单例模式,是特别常见的一种设计模式,因此我们有必要对它的概念和几种常见的写法非常了解,而且这也是面试中常问的知识点. 所谓单例模式,就是所有的请求都用一个对象来处理,如我们常用的Spring默认就是单 ...

  • 详解JAVA面向对象的设计模式 (一)、单例模式

    本系列,记录了我深刻学习设计模式的过程.也算是JAVA进阶学习的一个重要知识点吧. 与设计相关的代码会贴出,但是基础功能的代码会快速带过.有任何错误的地方,都欢迎读者评论指正,感谢.冲冲冲! 单例模式 ...

  • 设计模式之单例模式(Singleton Pattern)

    一.定义 一个类只有一个实例,且该类能自行创建这个实例的一种模式. 二.单例模式举例 例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各 ...

  • PHP设计模式之单例模式

    PHP设计模式之单例模式 单例模式绝对是在常用以及面试常问设计模式中排名首位的.一方面它够简单,三言两语就能说明白.另一方面,它又够复杂,它的实现不仅仅只有一种形式,而且在Java等异步语言中还要考虑 ...

  • [PHP小课堂]PHP设计模式之单例模式

    [PHP小课堂]PHP设计模式之单例模式 关注公众号:[硬核项目经理]获取最新文章 添加微信/QQ好友:[DarkMatterZyCoder/149844827]免费得PHP.项目管理学习资料

  • 【设计模式】单例模式(Singleton Pattern)

    懒汉式 public class Singleton { private static Singleton instance; private Singleton() {}; public stati ...

  • 设计模式(2) 单例模式

    单例模式 线程安全的Singleton 会破坏Singleton的情况 线程级Singleton 单例模式是几个创建型模式中最独立的一个,它的主要目标不是根据客户程序调用生成一个新的实例,而是控制某个 ...

  • 设计模式之单例模式(Singleton Pattern),太简单了

    基本定义 单例模式就是确保某一个类只有一个实例,并且提供一个全局访问点.单例模式有如下几个特点: 它只有一个实例. 它必须要自行实例化. 它必须自行向整个系统提供访问点. 代码实现 饿汉式 直接初始化 ...