单例模式
Singleton Pattern
- 单例模式概述
- 单例模式的结构与实现
- 单例模式的应用实例
- 饿汉式单例与懒汉式单例
- 单例模式的优缺点与适用环境
- 多例模式Multiton
- 反射对单例模式破坏和枚举类型enum()
1.单例模式概述
Windows任务管理器
像Logger、SystemInfo、PrintServer、java的RunTime等
这些类在整个系统中只应存在一个实例。
如何保证一个类只有一个实例并且这个实例易于被访问?
(1) 全局变量:可以确保对象随时都可以被访问,但不能防止创建多个对象
(2) 让类自身负责创建和保存它的唯一实例,并保证不能创建其他实例,它还提供一个访问该实例的方法
- 单例模式的定义
单例模式:确保一个类 只有一个实例,并提供一个 全局访问点来访问这个唯一实例。 Singleton Pattern: Ensure a class has only one instance, and provide a global point of access to it.
要点:
- 某个类只能有一个实例
- 必须自行创建这个实例
- 必须自行向整个系统提供这个实例
单例模式的结构与实现
- 单例模式的结构
定义单例类MyClass
1. 使MyClass类的构造方法私有
private MyClass(){ //constructor code}
2.定义一个私有的、静态的、(不变的)MyClass类成员变量,类型是MyClass
private static (final) Myclass singletonOfMyClass
3. 定义一个访问成员变量的静态方法
public static MyClass getSingletonOfMyclass(){ return singletonOfMyClass ; }
Cannot make a static reference to the non-static field.
饿汉模式和懒汉模式
饿汉模式:当类被加载时,静态变量(singletonOfMyClass)会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。
懒汉模式:与饿汉模式不同,懒汉模式在第一次被引用时将自己实例化,在懒汉模式单例类被加载时不会将自己实例化。(延迟加载技术,避免多线程同时调用getSingletonOfMyClass,加上synchronized)
编发编程:原子性、可见性、有序性
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
《深入理解Java虚拟机》:
程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
锁定规则:一个 unLock 操作先行发生于后面对同一个锁的 lock 操作;
volatile 变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
对象终结规则:一个对象的初始化完成先行发生于他的 finalize() 方法的开始;
对象在初始化的时候分三个步骤:
memory = allocate(); //1. 分配对象的内存空间
ctorInstance(memory); //2. 初始化对象
instance = memory; //3. 设置 instance 指向对象的内存空间
因为步骤 2 和步骤 3 需要依赖步骤 1,而步骤 2 和 步骤 3 并没有依赖关系,所以这两条语句有可能会发生指令重排,也就是或有可能步骤 3 在步骤 2 的之前执行。在这种情况下,步骤 3 执行了,但是步骤 2 还没有执行,也就是说 instance 实例还没有初始化完毕,正好,在此刻,线程 2 判断 instance 不为 null,所以就直接返回了 instance 实例,但是,这个时候 instance 其实是一个不完全的对象,所以,在使用的时候就会出现问题。
饿汉模式与懒汉模式比较:
饿汉式单例类在类被加载时就将自已实例化,它的优点在于无须考虑多个线程同时访问的问题,可以确保实例的唯一性; 从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。
懒汉式单例类在第一次使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及费源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的几率变得较大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到定影响。
解决方法
使用静态内部类实现单例模式(需要语言支持)
Java可以通过Initialization on Demand Holder (IoDH)
public class Singleton {private Singleton(){};//静态内部类private static class HolderClass{private static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance(){return HolderClass.INSTANCE;}public static void main(String[] args){Singleton s1,s2;s1=Singleton.getInstance();s2=Singleton.getInstance();System.out.println(s1==s2);}}
不同语言在实现单例模式虽然有区别,但是单例模式的意图是一致的
确保对象唯一!
未完待续.......