设计模式(二)-------->单例模式
来源:互联网 发布:致远软件 编辑:程序博客网 时间:2024/04/30 07:16
单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。
饿汉模式
例子:
public class Singleton { /* 持有私有静态实例,防止被引用*/ private static Singleton instance = new Singleton(); /* 私有构造方法,防止被实例化 */ private Singleton() { } /* 静态工程方法,返回Singleton实例 */ public static Singleton getInstance() { return instance; } }
这个类是可以实现单例模式的,但是存在不少问题,比如在类中不管用户是否要使用该类的对象,就先创建好了一个实例放在内存中。
懒汉模式
public class Singleton { /* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载 */ private static Singleton instance = null; /* 私有构造方法,防止被实例化 */ private Singleton() { } /* 静态工程方法,创建实例 */ public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这个类可以满足基本要求,但是,像这样毫无线程安全保护的类,如果我们把它放入多线程的环境下,肯定就会出现问题了,如何解决?
第三种单例(线程安全)
我们首先会想到对getInstance方法加synchronized关键字,如下:
public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } 但是,synchronized作为修饰符在方法上使用,在性能上会有所下降,因为每次调用getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加锁,之后就不需要了,所以,这个地方需要改进。
第四种,双重检查
我们改成下面这个:
/**
* 轻量级的锁
*volatile 经常用于多线程程序中修饰变量
* 作用是表示即使从内存中获取变量的值而没有延时
*/
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
似乎解决了之前提到的问题,将synchronized关键字加在了方法内部,也就是说当调用的时候是不需要加锁的,只有在instance为null,并创建对象的时候才需要加锁,性能有一定的提升。但是,这样的情况,还是有可能有问题的。
看下面的情况:在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句并非是一个原子操作,在 JVM 中这句代码大概做了下面 3 件事情:
1给 new的对象 分配内存
2调用 Singleton 的构造函数来初始化成员变量
3将引用instance指向分配的内存空间(执行完这步 instance 就为非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,另外一个线程B抢夺到了CPU的执行权,这时instance已经是非null了(但却没有初始化),所以线程B会直接返回 instance,然后使用,结果就会出现问题了(因为对象还没有初始化)。
还有另外一种解决方案:使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的(就是加载完毕后别的线程才能使用)。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。同时该方法也只会在第一次调用的时候使用互斥机制,这样就解决了低性能问题。就是第五种方式。
第五种 静态内部类
例如:
public class Singleton {
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
/* 获取实例 */
public static Singleton getInstance() {
return SingletonFactory.instance;
}
}
但是如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。所以说,十分完美的东西是没有的,我们只能根据实际情况,选择最适合自己应用场景的实现方法。
第六种,针对序列化与反序列化
这里还有一个问题,就是如果把单例对象进行序列化然后再反序列化,那么内存中就会出现俩个一样的单例对象,只是内存地址不同。这种情况我们可以使用readResolve方法来防止。
private Object readResolve(){.....}ObjectInputStream 会检查对象的class是否定义了readResolve方法。如果定义了,将由readResolve方法指定返回的对象。返回对象的类型一定要是兼容的,否则会抛出ClassCastException 。例子:public abstract class Singleton implements Serializable{ private static final long serialVersionUID = 1L; /* 此处使用一个内部类来维护单例 */ private static class SingletonFactory { @SuppressWarnings("serial") private static Singleton instance = new Singleton(){}; } //测试方式,把单例对象序列化后再反序列化从而获得一个新的对象 就相当于复制了一个单例对象 public Singleton copy() throws Exception{ ByteArrayOutputStream os = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(Singleton.getInstance()); InputStream is = new ByteArrayInputStream(os.toByteArray()); ObjectInputStream ois = new ObjectInputStream(is); Singleton obj = (Singleton) ois.readObject(); return obj; } /* 获取实例 */ public static Singleton getInstance() { return SingletonFactory.instance; } /* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */ /* 把这个方法注释前和注释后来运行测试代码观察结果 */ private Object readResolve() { return getInstance(); } }
第七种 枚举实现
public enum SingleTon { INSTANCE; }
- 模式设计(二)------单例模式
- 设计模式之二--- 单例模式
- C++设计模式[二]单例模式
- 设计模式(二)--单例模式
- 设计模式(二)单例模式
- 【设计模式】二、单例模式
- 设计模式(二)-------->单例模式
- 设计模式(二):单例模式
- 设计模式之单例模式(二)
- 二、设计模式之一-----单例模式
- 【设计模式 二】单例模式
- c++设计模式二,单例模式
- 设计模式(二)单例模式
- 设计模式<二>单例模式
- 设计模式<二>单例模式
- 设计模式(二)单例设计模式
- 设计模式利剑二--单例模型
- 单例设计模式一、二
- 大规模分布式存储系统读书笔记前三章(未完)
- 计算器(难度系数:3颗星)
- 装Ubuntu系统制作U启动
- 怎样衡量一份工作是否值得坚持?
- gnu/stubs-soft.h: 没有那个文件或目录
- 设计模式(二)-------->单例模式
- c++实验四
- 华为2017笔试题 公司年会
- 【qscoj】喵哈哈村的狼人杀大战(1)
- Spring事务之五(应用场景分析)
- WIN7和VMware+Ubuntu12.04LTS文件共享,无法看到共享文件
- Lua元表的基础示例。
- 461. Hamming Distance
- 【C语言】字节对齐和边界对齐