单例模式
特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。。
线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。
类型:
- 懒汉式单例
/* 1、没有考虑线程安全的代码 */
public class Singleton {
private Singleton() {}
private static Singleton single=null;
//静态工厂方法
public static Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
}
注意: Singleton通过将构造方法限定为private避免类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。如果通过Java反射机制是能够实例化构造方法为private的类,即所有的Java单例实现均会失效。
以上懒汉式单例的实现没有考虑线程安全问题,并发环境很可能出现多个Singleton实例。要实现线程安全,有如下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全。
/* 2、考虑线程安全的代码,添加同步方法 */
public static synchronized Singleton getInstance() {
if (single == null) {
single = new Singleton();
}
return single;
}
/* 3、考虑线程安全的代码,添加双重锁定,但仍有问题 */
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
注意: 代码3中, Java 平台内存模型允许所谓的无序写入,这也是失败的一个主要原因。
为解释该问题,需要重新考察上述清单中的 第6 行。此行代码创建了一个 Singleton 对象并初始化变量 instance 来引用此对象。这行代码的问题是:在 Singleton 构造函数体执行之前,变量 instance 可能成为非 null 的,即赋值语句在对象实例化之前调用,此时别的线程得到的是一个还未初始化的对象,这样会导致系统崩溃。
/* 四、静态内部类 */
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
- 饿汉式单例
//饿汉式单例类.在类初始化时,已经自行实例化
public class Singleton {
private Singleton() {}
private static final Singleton1 single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
资源加载和性能:
饿汉式单例在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成。
懒汉式单例,延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。
根据个人经验以及参考线上文字整理而成,欢迎拍砖&分享&联系本人。
[参考阅读]
那些年,我们一起写过的“单例模式”