- 单例模式:
单例模式涉及到一个单一的类,在系统运行中,确保这个类只有一个实例。而且这个实例是这个类自己创建的或者实例化的。也就是说这个类提供了一种访问其唯一对象的方式,可直接访问不需要实例化。单例模式是JAVA的23中设计模式中最简单的模式,但是在很多场景下需要使用单例模式。 - 单例模式的特定:
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例。
- 单例类必须给所有其他对象提供这一实例。
- 单例类示意图:
引用自《单例模式》
- 单例模式的示例代码:
因为单例模式有多种实现方式,这里一一列举出来:
- 饿汉式: 所谓饿汉式,顾名思义就是比较饥饿,所以会迫不及待的想创建对象,因此这种方式在被加载的时候就实例化创建了类。这种方式是典型的空间交换时间,就是不管这个类,在程序中是否使用,在类加载的时候就先创建出来,这样虽然节省了时间,但是会造成内存上的浪费。具体实现代码如下:
public class SingletonForLazy {
//创建 SingletonForLazy 的一个对象(饿汉式关键代码)
private static SingletonForLazy singleton= new SingletonForLazy();
//私有化构造方法
private SingletonForLazy() {
}
//提供获取唯一对象的方法
public static SingletonForLazy getInstance(){
return singleton;
}
}
- 懒汉式: 和饿汉式的区别是创建对象的时间不同,一般是在对象被调用的时候才会真正的实例化对象,如果没有被调用则不实例化。这一种用时间换空间的方式,是每次获取实例都先进行判断,看实例是否存在,这样的判断需要花费一定的时间,但是,如果实例存在则不需要再重复创建,这样可以减少系统内存的浪费。具体实例代码如下:
public class SingletonForHunger {
//初始化SingletonFoHunger对象为空
private static SingletonForHunger singleton= null;
//私有化构造方法
private SingletonForHunger() {
}
//提供获取唯一对象的方法,当外部调用该方法时开始初始化
public static SingletonForHunger getInstance(){
if (singleton==null) {
singleton = new SingletonForHunger();
}
return singleton;
}
}
但是以上这种方式在多线程的情况下是不安全的,会产生多个singleton对像。因为没有加锁(synchronized)。所以我们说这个从严格定义上来说不能实现单例模式。
对以上代码改造如下:
public class SingletonForSyn {
//初始化SingletonFoHunger对象为空
private static SingletonForSyn singleton= null;
//私有化构造方法
private SingletonForSyn() {
}
//提供获取唯一对象的方法,当外部调用该方法时开始初始化
public static synchronized SingletonForSyn getInstance(){
if (singleton==null) {
singleton = new SingletonForSyn();
}
return singleton;
}
}
改造成以上这种代码后,通过synchronized的锁,使得线程变得安全,但是由于锁的特性会使得代码的效率大大降低。因此一般也不建议使用这种单例模式的实现方式。
- 双重检查加锁: getInstance方法并不是每次都需要执行,而是先不执行,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。而且该种方法使用
volatile
来修饰对象。具体实例代码如下:
public class SingletonForDCL {
//初始化SingletonFoHunger对象为空
private volatile static SingletonForDCL singleton= null;
//私有化构造方法
private SingletonForDCL() {
}
//提供获取唯一对象的方法,当外部调用该方法时开始初始化
public static SingletonForDCL getInstance(){
//检查是否实例化
if (singleton==null) {
//线程安全的创建实例
synchronized (SingletonForDCL.class) {
//二次检查是否存在 不存在才正式创建实例
if (singleton == null) {
singleton = new SingletonForDCL();
}
}
}
return singleton;
}
}
双重检查加锁法是能够做到效率和安全的双重保护,但是该方法对于JDK的版本有要求,需要从JDK1.5版本开始支持,JDK1.5之前该方法是无法保证线程安全的,从网上查询资料后得知,这是因为在1.5版本后禁止指令重排优化这条语义才正常工作的。
- 静态内部类: 这种方法能达到的效果和双重检查加锁法是一致的,但是实现起来更加简单。这种方法是利用了静态类只会加载一次的机制,使用静态内部类持有单例对象,达到单例的效果。示例代码如下:
public class SingletonForClass {
//构建静态内部类
private static class SingleTonHolder {
public static final SingletonForClass singletonForClass = new SingletonForClass();
}
//直接返回对象
public static SingletonForClass getInstance(){
return SingleTonHolder.singletonForClass;
}
}
以上代码的实现方式解释如下:
当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.singletonForClass,导致SingletonHolder类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建Singleton的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。
引用自《JAVA与模式》之单例模式
- 枚举法: 枚举法是现阶段实现单例模式的最优雅最简洁的方法,但是这种方法同样对于JDK的版本有要求,要求在JDK1.5版本之后。因为JDK1.5才新增enum关键字用于定义枚举类,具体示例例代码如下:
- 需要被实例化的对象:
public class SingleObj {
public SingleObj() {
System.out.println(">>>>>>>>>>>>>.create");
}
}
- 枚举类:
public enum SingletonForEnum {
SINGLEINSTANCE;
private SingleObj singleObj;
private SingletonForEnum() {
singleObj = new SingleObj();
}
public SingleObj getInstance() {
return singleObj;
}
}
- 测试代码类:
public class Test {
public static void main(String[] args) {
SingleObj singleObj = SingletonForEnum.SINGLEINSTANCE.getInstance();
}
}
枚举法的优势在于:枚举本身无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化。因此Effective Java推荐尽可能地使用枚举来实现单例。
但是枚举法实现单例不推荐在Android平台使用,因为相比较其他方式来说,在Android平台,枚举法会消耗更多的内存。一般在Android平台比较推荐的事静态内部类或者双重检查加锁。