设计模式学习笔记(三)之 单例模式
今天是上班的第一天,由于没给安排活,也不知道干啥,于是随便翻翻csdn看到一篇关于单例模式的讲解,觉得写的很不错,讲的简单易懂,为了加深记忆(打发时间)写下这篇博客。
原博客地址:单例模式
1、什么是单例模式
从书上讲的来说,单例模式,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式,单例模式是指在内存中只会创建且仅创建一次对象的设计模式。简单来说就是整个程序有且仅有一个实例。
2、单例模式的两种类型
创建的单例模式有两种类型:
- 懒汉式:需要时再去创建对象
- 饿汉式:不管是否需要,在类加载的时候就创建单例对象
2.1 懒汉式
懒汉式在创建对象前需要进行判断对象是否实例化,如果已有实例化对象直接返回,没有则new一个后返回。
代码如下:
public static Singleton{
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance(){
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}2.2 饿汉式
饿汉式在类加载时就创建好实例化的对象,待程序需要时直接返回该对象。
代码如下:
public static Singleton{
private static final Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance(){
return singleton;
}
}到此两种类型的单例模式都已讲解完成,但是仍然存在某些问题,试想想,在多线程并发的情况下饿汉式单例模式还能正常使用,而懒汉式单例模式真的只有一个实例对象嘛?
3、懒汉式单例模式的优化改进
在并发的情况下,会存在还没有实例对象而多个线程进入到判断实例对象是否为null这一步,由于还没有创建实例对象,这些线程都会去创建实例,这样就出现了问题,所以我们需要对该步进行加锁!
改进后代码如下:
public static Singleton{
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance(){
if (singleton == null) {
synchronized(Singleton.class) { // 某个线程获得该锁进行初始化
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}因为需要两次判空,且对类对象加锁,该懒汉式写法也被称为:Double Check(双重校验) + Lock(加锁)
上面这段代码已经近似完美了,但是还存在最后一个问题:指令重排(这块是看完这篇博客后才知道的)
4. 使用volatile防止指令重排
创建一个对象,在JVM中会经过三步:
(1)为singleton分配内存空间
(2)初始化singleton对象
(3)将singleton指向分配好的内存空间
指令重排序是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能
在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤,线程B判断singleton已经不为空,获取到未初始化的singleton对象,就会报NPE异常。
使用volatile关键字优化后的代码:
public static Singleton{
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance(){
if (singleton == null) {
synchronized(Singleton.class) { // 某个线程获得该锁进行初始化
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}5、总结
单例模式分为懒汉式和饿汉式两种写法,之前课堂上讲的由于没怎么听过对这些都不太熟悉,写完这篇博客后算是基本了解单例模式了,以后在实际代码中用到时也不会显得迷茫,要去考虑多线程的这种情况,因为实际项目和理想上的情况总是不一样的,需要考虑全面!
注意事项:
(1)在开发中如果对内存要求比较高,那么使用懒汉式写法,可以在特定时候才创建该对象;
(2)不必考虑内存情况则推荐使用饿汉式写法,因为简单不易出错!且没有任何并发安全和性能问题
(3)为了防止多线程环境下,因为指令重排序导致变量报NPE,需要在单例对象上添加volatile关键字防止指令重排序