设计模式——单例模式

目录

1、饿汉式

2、懒汉式

3、Double CheckLock(双重锁判断机制)

4、静态内部类

5、枚举


1、饿汉式

public class ImageLoader{  
    private static ImageLoader instance = new ImageLoader;  
    private ImageLoader(){}  
    public static ImageLoader getInstance(){   
        return instance;   
    }  
}

        饿汉式顾名思义,就是这个汉子很饿,一上来就把单例对象创建出来了,要用的时候直接返回即可,这种可以说是单例模式中最简单的一种实现方式。但是问题也比较明显。单例在还没有使用到的时候,初始化就已经完成了。也就是说,如果程序从头到位都没用使用这个单例的话,单例的对象还是会创建。这就造成了不必要的资源浪费。所以不推荐这种实现方式。

2、懒汉式

public class ImageLoader{  
    private static ImageLoader instance;  
    private ImageLoader(){}  
    public static synchronized ImageLoader getInstance(){   
        if(instance == null){   
            instance = new ImageLoader();   
        }   
        return instance;  
    }  
}

        懒汉式也顾名思义,就是这个汉子比较懒,一开始的时候什么也不做,知道要使用的时候采取创建实例的对象。看起来还不错,只有在使用实例的时候,我们才回去创建对象。但是细心的同学可能发现了,我们在获取实例的方法上加了锁,避免多线程引发的创建多个单例的情况。多线程的问题是避免了,但也造成了整体性能的下降,每次使用单例对象,都需要锁判断,降低了整体性能。很明显,懒汉式也不是我们所要追求的目标。

3、Double CheckLock(双重锁判断机制)

public class ImageLoader{  
    private static ImageLoader instance;  
    private ImageLoader(){}  
    public static ImageLoader getInstance(){  
        if(instance == null){   ①
            synchronized (ImageLoader.class){  
                if(instance == null){  
                    instance = new ImageLoader();  
                }  
            }  
        }   
        return instance;   
    }  
} 

        第一次访问,实例化与懒汉式相同,需要加锁;之后(第二、三…次)的访问,不需要加锁,提高效率。

        可以看到,在获取单例对象的时候,我们先进行了两为空判断,并且在第二次判断前加了锁,这就让程序变得更加优秀,在使用的时候,只会前几次获取单例对象的时候会进行锁判断,一旦单例对象创建完成,锁的任务也就完成了,在懒汉式的基础上,提高了性能。DCL是使用最多的单例实现方式,能够在使用的时候才进行单例对象的初始化创建,并且能够在绝大多数情况下保证对象的唯一性的正确性。请注意,是绝大多数情况下,也就是说,这种模式也不能完全保证单例的对象的完美实现,但是,就一般情况下,这种模式都能满足需求。

        存在的问题是无序性。new操作是无序的(指令重排),它可能会被编译成:

  • 先分配内存,让instance指向这块内存

  • 在内存中创建对象

        然而我们需要意识到这么一个问题,synchronized虽然是互斥的,但不代表一次就把整个过程执行完,它在中间是可能释放时间片的,时间片不是锁。也就是说可能在a执行完后,时间片被释放,线程2执行到①,这时他读到的instance是不是null呢?

        基于可见性,可能是null,也可能不是null。

        给instance 加上volatile(保证可见性和禁止指令重排)可以解决无序性

public volatile static ImageLoader instance;

        要区分jdk版本,

        在jdk1.4及之前,volatile并不能保证new操作的有序性,但是它能保证可见性,因此标记1处,读到的不是null,导致了问题。

        从1.5开始,加了volatile关键字的引用,它的初始化就不能是:

  • 先分配内存,让instance指向这块内存

  • 在内存中创建对象

        而应该是:

  • 在内存中创建对象

  • 让instance指向这个对象.

        这种形式,也就避免了无序性问题。

4、静态内部类

public class ImageLoader {
    private static class InnerInstance {
        private static final ImageLoader instance = new ImageLoader();
    }
    private ImageLoader() {
    
    }
    public static ImageLoader getInstance() {
        return InnerInstance.instance;
    }
}

        这种方式,并未加锁,因为第一次加载ImageLoader类时,并不会实例化单例对象,只有第一次调用getInstance()方法时会导致虚拟机加载InnerInstance类,这种方式不仅能保证对象的单一性,还避免加锁带来的性能问题,又启动了延迟加载的优化,所以这就是单例模式的终极实现版本,也是推荐使用的方式。

        利用了classloader的机制来保证初始化instance时只有一个线程

5、枚举

class Resource {

}
public enum SomeThing {
    INSTANCE;
    private Resource instance;
    SomeThing() {
        instance = new Resource();
    }
    public Resource getInstance() {
        return instance;
    }
}

        在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值