饿汉式------立即加载
package singleDemo;
//饿汉式单例,在类初始化时,已自行初始化
public class Hungry {
private Hungry(){
}
private static Hungry hungry = new Hungry();
//静态工厂方法
public static Hungry getInstance(){
return hungry;
}
}
为什么饿汉式时线程安全的?、
类加载的方式是按需加载,且只加载一次。因此,在上述单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。换句话说,在线程访问单例对象之前就已经创建好了。再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。因此就说,饿汉式单例天生就是线程安全的。
如果长时间没有用到getInstance方法,不需要Hungry类的对象,这不是一种浪费吗?
饿汉式是最简单的单例模式的写法,保证了线程的安全,在很长的时间里,我都是饿汉模式来完成单例的,因为够简单,后来才知道饿汉式会有一点小问题,看下面的代码:
懒汉式------>延迟加载
线程不安全的
public class Hungry {
private Hungry(){
}
private static Hungry hungry =null;
public static Hungry getInstance(){
if (hungry==null){
hungry = new Hungry();
}
return hungry;
}
}
懒汉式为什么是线程不安全的?
我们知道懒汉式的单例模式,创建对象的时机在第一次调用getInstance()方法。而安全隐患就存在这时间段,倘若有两条线程都是运行该代码段,一条线程运行过程进入了if语句块且还没有把创建对象的实例赋值给成员变量时恰好进入了阻塞状态(还不清楚线程生命周期的小伙伴可理解为该线程暂停了)。由于if语句判断条件依据是:instance == null才能进入该语句块。那么另一条线程就有可能会混进在该if语句块,最后就会导致两个线程创建了两个不一样的对象。
改写后
多加一层检测可以避免问题,也就是DCL懒汉式!
package singleDemo;
public class Hungry {
private Hungry(){
}
private static Hungry hungry =null;
public static Hungry getInstance(){
//第一次检查
if (hungry==null) {
//模拟出现初始化时的操作
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Hungry.class) {
//第二次检查
if (hungry==null) {
hungry = new Hungry();
}
}
}
return hungry;
}
}
DCL懒汉式的单例,保证了线程的安全性,又符合了懒加载,只有在用到的时候,才会去初始化,调用效率也比较高,但是这种写法在极端情况还是可能会有一定的问题。因为
hungry = new Hungry();
不是原子性操作,至少会经过三个步骤:
- 分配对象内存空间
- 执行构造方法初始化对象
- 设置instance指向刚分配的内存地址,此时instance !=null;
由于指令重排,导致A线程执行hungry = new Hungry();的时候,可能先执行了第三步(还没执行第二步),此时线程B又进来了,发现hungry 已经不为空了,直接返回了hungry ,并且后面使用了返回的hungry ,由于线程A还没有执行第二步,导致此时hungry 还不完整,可能会有一些意想不到的错误,所以就有了下面一种单例模式。
这种单例模式只是在上面DCL单例模式增加一个volatile关键字来避免指令重排:
public class Hungry {
private Hungry(){
}
private volatile static Hungry hungry =null;
public static Hungry getInstance(){
if (hungry==null) {
synchronized (Hungry.class) {
if (hungry==null) {
hungry = new Hungry();
}
}
}
return hungry;
}
}