1、动机与定义
系统中有些资源仅仅能有一个,或者一个就够。多个浪费。
比如一个系统仅仅能有一个窗体管理器或文件系统、一个系统仅仅能有一个计时器或序号生成器、web系统仅仅能有一个页面计数器等等。此时。最好就须要把这些资源设置成有且仅有一个实例。
代码中也就是怎样保证一个类仅仅有一个实例而且这个实例可以被訪问呢?仅仅有一个实例的就意味着不能让其它类来实例化。也就是仅仅能自己实例化自己。可以被訪问也就意味着自身要对外提供全局方法来获取到这个实例。这就是单例模式。
单例模式定义:确保某一个类仅仅有一个实例,并且自行实例化并且向整个系统提供这个实例。
单例模式通常代表着系统具有唯一性的资源。主要有3点:仅仅有一个实例。自行创建这个实例;自行向整个系统提供这个实例。
2、结构与类图
单例模式是创建型模式,事实上结构很easy。须要注意下面3点:
1、构造方法私有:不让外部实例化。仅仅能将构造函数私有;
2、提供一个公共静态方法获取实例:获取这个实例前是没有实例的,仅仅能用静态的。
3、实例保存到自身静态私有属性上:获取方法是静态的,实例当然也仅仅能是静态的,最好是final的,单例不同意改动;
通用类图例如以下:
![](file:///C:/Users/ZHANGW~1.ETH/AppData/Local/Temp/enhtmlclip/Image.png)
代码例如以下:
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance ; } }
3、适用场景及效果(优缺点)
1、仅仅须要1个实例,多了浪费。主要用于节约系统资源,创建一个对象须要消耗过多资源时,考虑将这个对象缓存,设计成单例的,如创建某些程序启动配置对象读取、操作系统的文件系统等。仅仅须要创建一个就够了,多了浪费;
2、仅仅须要1个实例。多了出错。如计数器。唯一序列号生成器等;
3、单例意味着多线程使用(假设单线程使用,单例全然没有意义了)。多线程下能够控制单一共享资源的訪问和线程间通讯。避免对同一资源的多重占用,如仅有1个打印机,各个线程自行调用会对一个资源多重占用,单例模式能够统一管理对打印机的訪问。还有如数据库连接池、线程池、日志应用等。
4、大量无状态的类实例,如须要大量静态常量或方法(有时也能够定义成static)能够考虑使用单例模式。如web开发中的service层。都是业务无状态的逻辑处理类,还有工具类和方法等。都能够设计成单例模式。这也是Spring框架中配置的bean默认都是单例的。
长处(使用后的效果):
1、单例仅仅有一个实例。也仅仅创建一次。能够节约系统资源,特别当这个对象须要频繁地创建和销毁时,并且创建和销毁要比較多的资源时;
2、能避免对单一资源的多重占用,进行统一管理。
3、单例模式能够在系统设置全局訪问点。优化和共享资源訪问。
缺点:
1、没有接口。扩展困难,无法适应变化。基本上仅仅能改动源代码。(为什么没接口,就一个实例。接口没意义);
2、測试麻烦,单例没完毕,无法測试。
3、与单一职责冲突。
单例模式能够分为有状态的和无状态的。无状态的单例对象不可变的,一般就是提供一些工具方法,有状态的单例对象是可变的。经常使用来给系统当作状态库。提供一些状态,如序列号生成器等。
4、演示样例
比方要做一个页面计数器。能够使用单例模式,很easy,直接看代码
//页面计数器 public class PageCounter { private static final PageCounter instance = new PageCounter(); // 计数器 private AtomicLong counter = new AtomicLong(0); private PageCounter() { } public static PageCounter getInstance() { return instance ; } public void add() { counter.getAndAdd(1); } public long get() { return counter.get(); } }
5、模式扩展
说到单例模式。非常多人想到的是怎样创建单例模式,有非常多种创建方法。懒汉、恶汉、双重锁等等,此处大概介绍一下。
第一种(饿汉)
//饿汉模式(推荐),类载入时就创建了 //长处:1、线程安全;2、调用getInstance时速度快 //缺点:1、无法延迟载入。2、有可能浪费资源,无人调用getInstance()时,仍然创建了实例 public class Singleton01 { private static final Singleton01 instance = new Singleton01(); private Singleton01() { } public static Singleton01 getInstance() { return instance ; } }另外一种(饿汉变种)
//饿汉模式变种,类载入时就创建了。和上一个模式差别不大。仅仅是能在static中增加逻辑处理 public class Singleton02 { private static Singleton02 instance = null; static { // 此处能够写一些逻辑 instance = new Singleton02(); } private Singleton02() { } public static Singleton02 getInstance() { return instance ; } }第三种(懒汉)
//懒汉(线程不安全),用到了再去初始化 //长处:延迟载入 //缺点:致命的并发问题,可能导致创建多次 public class Singleton03 { private static Singleton03 instance = null; private Singleton03() { } public static Singleton03 getInstance() { if ( instance == null ) { // 此处有并发问题 instance = new Singleton03(); } return instance ; } }第四种(懒汉变种)
//懒汉(线程安全) //长处:延迟载入 //缺点:效率低下,初始化完成后,getInstance()方法根本不须要同步了 public class Singleton04 { private static Singleton04 instance = null; private Singleton04() { } public synchronized static Singleton04 getInstance() { if ( instance == null ) { instance = new Singleton04(); } return instance ; } }第五种(双重锁定检查)
//双重锁定检查 //长处:延迟载入。效率高 //缺点:jdk1.5之后才干够用 //因为jdk1.5之前编译器同意处理器乱序运行,所以可能导致获取到没初始化完成的instance public class Singleton05 { private static Singleton05 instance = null; private Singleton05() { } public static Singleton05 getInstance() { if ( instance == null ) { synchronized (Singleton05.class) { if ( instance == null ) { instance = new Singleton05(); } } } return instance ; } }第六种(枚举)
//枚举方式(推荐)。Effective Java作者Joshua Bloch推荐的方式 //长处:不仅能避免线程同步问题。还能防止反序列化生成新的对象,相当严谨 //最基本的是很的简单 //缺点:枚举是jdk1.5之后增加的特性,对版本号有要求 public enum Singleton06 { instance; public void someMethod() { // 业务逻辑方法 } }第七种(静态内部类)
//静态内部类 //长处:解决线程安全问题。并且能够延迟载入,基本上是以前最好的办法 //缺点:代码复杂 public class Singleton07 { private static class RealSingleton { static final Singleton07 instance = new Singleton07(); } public static Singleton07 getInstance() { return RealSingleton.instance; } }单例模式创建方法有非常多种,没有最好的,仅仅有最合适的,比方第七种方法比較好,可是不是必需为了一个不会出现的问题而使用非常复杂的第七种模式。假设没有须要延迟载入的地方(如读取配置文件等)。推荐第一种模式,假设是JDK1.5以上。推荐使用枚举的方法。
单例模式还有个地方要注意。仅仅有1个实例。尽管构造函数私有化。外边不能new了。可是还有其它方式创建对象实例。如反序列化时,可能得到还有一个实例,此时就要考虑序列化对单例的影响,还有不同类载入器(ClassLoader)对单例的影响等都要考虑。
事实上就是创建方式要支持的级别,这就须要依据实际情况。选择你的创建方式了:
1、每次从getInstance()都能返回一个且唯一的一个对象。
2、希望这种方法能适应多线程并发訪问。
3、并发时方法性能尽可能高。
4、实现延迟载入(Lazy Load)。在须要的时候才被构造,并且要可以处理业务逻辑。
5、可以处理多ClassLoader、多JVM,防止反序列化等情况。
5、可以处理多ClassLoader、多JVM,防止反序列化等情况。