饿汉式
实现方式
class EagerSingleton {
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() { }
public static EagerSingleton getInstance() {
return instance;
}
}
懒汉式
实现方式(线程安全版本)
class LazySingleton {
private volatile static LazySingleton instance = null;
private LazySingleton() { }
public static LazySingleton getInstance() {
//第一重判断
if (instance == null) {
//锁定代码块
synchronized (LazySingleton.class) {
//第二重判断
if (instance == null) {
//创建单例实例
instance = new LazySingleton();
}
}
}
return instance;
}
}
PS:
为什么要双重检查呢?
假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想.
比较
饿汉式 | 懒汉式 | |
---|---|---|
占用内存情况 | 类被加载就占用着内存 | 被需要用的时候才初始化 |
线程安全 | 类被加载的时候就已经实例化, 不用考虑多线程访问问题 | 需要双重检查锁来保证线程安全 |
性能 | 首次访问的时候涉及实例化, 实例化可能耗费大量时间,性能自然会降低 |
一个更好的单例实现方法
饿汉式耗内存, 懒汉式耗性能,这里有个方式结合2者优点: 称为Initialization on Demand Holder(IoDH)技术
翻译成中文好像叫按需初始化…
说人话
就是使用静态内部类
public class Singleton {
public static Singleton getInstance(){
return Inner.instance;
}
private Singleton(){
}
//内部类,默认不加载
static class Inner {
private final static Singleton instance = new Singleton();
}
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2);
}
}
运行上述代码, 运行结果是true, 即创建的2个对象是同一个对象. 由于静态单例对象没有作为Singleton的成员变量直接初始化, 因此类加载的时候不会实例化Singleton(长期占据内存), 第一天调用getInstance()时将加载内部类Inner, 在该内部类中定义了一个static类型的变量instacne, 此时会首先初始化一这个成员变量,由于Java虚拟机来保证其线程安全性
, 确保该成员变量只能初始化一次. 由于getInstance()方法没有任何线程锁定, 因此性能不会造成任何影响.