单例模式-Java实现-非延迟加载、延迟加载

 
单例的创建有两种方式:
 
1、非延迟加载:不管什么时候要使用,先提前创建实例。
 
2、延迟加载:等到真正要使用的时候才去创建实例,不用时不要去创建。
 
 

第一种:非延迟加载


 
public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {
    }
    
    public static Singleton getInstance() {
        return instance;
    }
}

 

第二种:同步延迟加载


 
public class Singleton {
    private static Singleton instance = null;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

多线程情况下,每一次获取,都需要获取同步锁,导致执行效率低下。

 

第三种:双重检测同步延迟加载


 
public class Singleton {
    private volatile static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {    // 1
                if (instance == null) {         // 2
                    instance = new Singleton(); // 3    (旧版本:还没初始化完成,就已经对其它线程可见了)
                }
            }
        }
        return instance;
    }
}

只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要获取同步锁了。

注意:JDK5.0以后版本中 instance 为 volatile, 双重检测同步延迟加载 才有效。
 
旧版本Java中, 双重检测同步延迟加载失败的主要原因是:Java内存模型中指令序列的“乱序执行”。
 
假设代码执行以下指令序列:
 
  1. 线程 1 进入 getInstance() 方法。
  2. 由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。
  3. 线程 1 前进到 //3 处,但在构造函数执行之前,使 instance 成为非 null。
  4. 线程 1 被线程 2 预占。
  5. 线程 2 检查 instance 是否为 null。因为 instance 不为 null,线程 2 将 instance 引用返回一个构造完整但部分初始化了的 Singleton 对象。
  6. 线程 2 被线程 1 预占。
  7. 线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。
为展示此情况的发生情况,假设代码行 instance =new Singleton(); 执行了下列伪代码:
 
mem = allocate();      //为单例对象分配内存空间

instance = mem;        //instance 引用指向内存,此时 instance 不为 null,但还未初始化

construct(instance);   //调用构造函数进行初始化

可以看出旧版本中, instance =new Singleton() 不具有操作的原子性。

可能原因如下(待验证):
 

Java 1.5 以前对实例变量是直接在主存中读写的,也就是以上 3 行伪代码的每一步操作,对于其它线程都是可见的。

Java 1.5 以后,JMM发生了根本性的改变,以上 3 行伪代码都在线程的工作空间中完成的,在没有向主存写入变量之前,其它线程对这个过程都是不可见的。volatile 关键字在这里有两个作用,一是禁止指令序列的“乱序执行”,让重排后的指令之间的依赖变得有序,必须让变量初始化完成后,才能够执行依赖于它的其它指令;二是执行完 3 行伪代码后,立即把变量 instance 写入到主存,该变量才对其它线程可见。

 

第四种:使用内部类实现延迟加载(推荐)


 
public class Singleton {
    private Singleton() {
        //实例的初始化
    }

    static class SingletonHolder {
        static Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}

静态内部类SingletonHolder只会被初始化一次,因为在被类加载器加载后,对SingletonHolder的Class对象只会进行一次初始化。

 

第五种:枚举


public enum Singleton {  
    INSTANCE;  

    public void fun() {  
    }  
}

 


 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值