一、什么是设计模式
是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
单例设计模式:
属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例,数学与逻辑学中,singleton定义为“有且仅有一个元素的集合”。
单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
Java单例模式例子(懒汉模式)
public class Singleton { private Singleton(){ } private static volatile Singleton instance = null; public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
二、单例模式的应用场景
举一个小例子,在我们的windows桌面上,我们打开了一个回收站,当我们试图再次打开一个新的回收站时,Windows系统并不会为你弹出一个新的回收站窗口。,也就是说在整个系统运行的过程中,系统只维护一个回收站的实例。这就是一个典型的单例模式运用。
我们在实际使用中并不存在需要同时打开两个回收站窗口的必要性。假如我每次创建回收站时都需要消耗大量的资源,而每个回收站之间资源是共享的,那么在没有必要多次重复创建该实例的情况下,创建了多个实例,这样做就会给系统造成不必要的负担,造成资源浪费。
再举一个例子,网站的计数器,一般也是采用单例模式实现,如果你存在多个计数器,每一个用户的访问都刷新计数器的值,这样的话你的实计数的值是难以同步的。但是如果采用单例模式实现就不会存在这样的问题,而且还可以避免线程安全问题。同样多线程的线程池的设计一般也是采用单例模式,这是由于线程池需要方便对池中的线程进行控制
同样,对于一些应用程序的日志应用,或者web开发中读取配置文件都适合使用单例模式,如HttpApplication 就是单例的典型应用。
从上述的例子中我们可以总结出适合使用单例模式的场景和优缺点:
适用场景:
- 1.需要生成唯一序列的环境
- 2.需要频繁实例化然后销毁的对象。
- 3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
- 4.方便资源相互通信的环境
三、单例模式的优缺点
优点:
- 内存中只有一个对象,节省内存空间。
- 避免频繁的创建销毁对象,可以提高性能。
- 避免对公共资源的多重占用,简化访问。
- 为整个系统提供一个全局访问点。
缺点:
- 不适用于变化频繁的对象。
- 单例模式滥用会带来一些问题。
- 如果实例化的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失;
四、单例模式的实现
需要:
(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。
(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。
(3)定义一个静态方法返回这个唯一对象。
单例设计模式有两种实现方式。
实现一:饿汉模式(立即加载)
立即加载就是使用类的时候已经将对象创建完毕(不管以后会不会使用到该实例化对象,先创建了再说。很着急的样子,故又被称为“饿汉模式”),常见的实现办法就是直接new实例化。
public class Singleton { // 将自身实例化对象设置为一个属性,并用static、final修饰 private static final Singleton instance = new Singleton(); // 构造方法私有化 private Singleton() {} // 静态方法返回该实例 public static Singleton getInstance() { return instance; } }
饿汉模式的优缺点:
优点:实现起来简单,没有多线程同步问题。
缺点:当类SingletonTest被加载的时候,会初始化static的instance,静态变量被创建并分配内存空间,从这以后,这个static的instance对象便一直占着这段内存(即便你还没有用到这个实例),当类被卸载时,静态变量被摧毁,并释放所占有的内存,因此在某些特定条件下会耗费内存。
实现二:延迟加载 / “懒汉模式”
延迟加载就是调用get()方法时实例才被创建(先不急着实例化出对象,等要用的时候才给你创建出来。不着急,故又称为“懒汉模式”),常见的实现方法就是在get方法中进行new实例化。
public class Singleton { // 将自身实例化对象设置为一个属性,并用static修饰 private static Singleton instance; // 构造方法私有化 private Singleton() {} // 静态方法返回该实例 public static Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
“懒汉模式”的优缺点:
优点:实现起来比较简单,当类SingletonTest被加载的时候,静态变量static的instance未被创建并分配内存空间,当getInstance方法第一次被调用时,初始化instance变量,并分配内存,因此在某些特定条件下会节约了内存。
缺点:在多线程环境中,这种实现方法是完全错误的,根本不能保证单例的状态。一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
实现三:线程安全的“懒汉模式”
如何实现懒汉式的线程安全?
加上synchronized即可
public class Singleton { // 将自身实例化对象设置为一个属性,并用static修饰 private static Singleton instance; // 构造方法私有化 private Singleton() {} // 静态方法返回该实例,加synchronized关键字实现同步 public static synchronized Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } }
但这样会降低整个访问的速度,而且每次都要判断。可以用双重检查加锁。
实现四:双重加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例。这是第二重检查。
双重加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
/** * 双重检查加锁的单例模式 * */ public class Singleton { /** * 对保存实例的变量添加volitile的修饰 */ private volatile static Singleton instance = null; private Singleton(){ } public static Singleton getInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块 if(instance == null){ //同步块,线程安全的创建实例 synchronized (Singleton.class) { //再次检查实例是否存在,如果不存在才真正的创建实例
if(instance == nul)
{
instance = new Singleton();
} } } return instance; } }
使用双重检测同步延迟加载去创建单例的做法是一个非常优秀的做法,其不但保证了单例,而且切实提高了程序运行效率
优点:线程安全;延迟加载;效率较高。
实现五:使用静态内置类实现单例模式
//阻止发生派生,而派生可能会增加实例 public sealed class Singleton { //在第一次引用类的任何成员时创建实例,公共语言运行库负责处理变量初始化 private static readonly Singleton instance=new Singleton(); private Singleton() { } public static Singleton GetInstance() { return instance; } }
实现六 枚举类
枚举enum和静态代码块的特性相似,在使用枚举类时,构造方法会自动被调用,也可以应用其他这个特性实现单例设计模式。
何时选用单例模式 当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题。