单例模式
定义:确保某一个类只有一个实例,自行实例化并且向整个系统提供这个实例。
使用场景:避免某个类产生多个对象而消耗过多的资源,确保某个类在程序中只有一个实例;
单例模式的优点:
- 对于频繁使用的对象,可以省略new操作花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;
- 由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC的压力,缩短GC停顿时间;
单例实现方式
方法一(懒汉式):
public class Singleton {
private Singleton(){
}
private static Singleton singleton = new Singleton();
public Singleton getInstance() {
return singleton;
}
}
说明:简单正确,但Singleton实例创建时机不受控制,任何对Singleton方法或字段的的引用都会导致类的初始化并创建instance实例,但是类的初始化只有一次,因此instance实例永远只会被创建一次;但是如果程序从头到位都没用使用这个单例的话,单例的对象还是会创建。这就造成了不必要的资源浪费
方法二(饿汉式):
public class LazySingleton {
private LazySingleton(){
}
private static LazySingleton instance = null;
public static synchronized LazySingleton getInstance() {
if (null == instance) {
instance = new LazySingleton();
}
return instance;
}
}
说明:并发场景下会有性能损耗
方法三(双重加锁DCL的实现方式,不推荐):
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton() {
}
public static LazySingleton getInstance() {
if (null == instance) {
synchronized (LazySingleton.class) {
if (null == instance) {//增加volatile后instance被实例化后立即可见
instance = new LazySingleton();
}
}
}
return instance;
}
}
这段代码只有一个问题 —— 它不能正常工作(甚至被称为“臭名昭著的双重锁检查”);最明显的原因是,初始化实例的写入操作和实例字段的写入操作能够被编译器或者缓冲区重排序,重排序可能会导致返回部分构造的一些东西。就是我们读取到了一个没有初始化的对象。这段代码还有很多其他的错误,以及为什么对这段代码的算法修正是错误的。在旧的java内存模型下没有办法修复它
“许多人认为使用volatile关键字能够消除双重锁检查模式的问题。在1.5的JVM之前,volatile并不能保证这段代码能够正常工作(因环境而定)。在新的内存模型下,实例字段使用volatile可以解决双重锁检查的问题,因为在构造线程来初始化一些东西和读取线程返回它的值之间有happens-before关系。
然后,对于喜欢使用双重锁检查的人来说(我们真的希望没有人这样做),仍然不是好消息。双重锁检查的重点是为了避免过度使用同步导致性能问题。从java1.0开始,不仅同步会有昂贵的性能开销,而且在新的内存模型下,使用volatile的性能开销也有所上升,几乎达到了和同步一样的性能开销。因此,使用双重锁检查来实现单例模式仍然不是一个好的选择。(修订—在大多数平台下,volatile性能开销还是比较低的)” ——引用自《http://ifeve.com/jmm-faq-dcl/》
方法四(推荐):静态内部类
既能享受类加载确保线程安全带来的便利,又能延迟加载的方式,就是静态内部类。Java静态内部类的特性是,加载的时候不会加载内部静态类,使用的时候才会进行加载。而使用到的时候类加载又是线程安全的
Initialization Demand Holder(IoDH)实现单例
public class StaticSingleton {
private StaticSingleton(){
}
private static class StaticSingletonHandler {
public static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance() {
return StaticSingletonHandler.instance;
}
}
由于静态单例对象没有作为Singleton的成员变量直接实例化,因此类加载时不会实例化Singleton,第一次调用getInstance()时将加载内部类HolderClass,在该内部类中定义了一个static类型的变量instance,此时会首先初始化这个成员变量,由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次。由于getInstance()方法没有任何线程锁定,因此其性能不会造成任何影响
通过使用IoDH,我们既可以实现延迟加载,又可以保证线程安全,不影响系统性能,不失为一种最好的Java语言单例模式实现方式
优点:
1.getInstance()方法中没有锁,高并发场景下性能优越;
2.getInstance()方法被调用时,StaticSingleton实例才被初始化;
方法五:枚举
在《Effective Java》最后推荐了这样一个写法,简直有点颠覆,不仅超级简单,而且保证了现场安全;JDK1.5提供了一个新的数据类型枚举。枚举的出现提供了一个较为优雅的方式取代以前大量的static final类型的变量
对于一个标准的enum单例模式,最优秀的写法还是实现接口的形式:
// 定义单例模式中需要完成的代码逻辑
public interface MySingleton {
void doSomething();
}
public enum Singleton implements MySingleton {
INSTANCE {
@Override
public void doSomething() {
System.out.println("complete singleton");
}
};
public static MySingleton getInstance() {
return Singleton.INSTANCE;
}
}