设计模式之单例模式

设计模式之单例模式,第1张

设计模式之单例模式

经过前面的Spring的学习,我认为首先要学习设计模式之后,再来研究Spring的源码。首先我们先来学习一下单例模式。

1、意图

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

2、动机

让类自身负责保存它的唯一实例,这个类可以保证没有其他实例可以被创建(通过截取创建新对象的请求),并且它可以提供一个访问该实例的方法,这就是Singleton模式。

3、适用性
  • 当类只能有一个实例而且客户可以从一个总所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类可扩展的,并且客户应该无须更改代码就能使用一个扩展的实例时。
4、结构

5、参与者
  • Singleton

--定义一个Instance *** 作,允许客户访问它的唯一实例。Instance是一个类 *** 作(即Smalltalk中的一个类方法和C++中的一个静态成员函数)。

--可能负责创建它自己的唯一实例。

6、协作
  • 客户只能通过Singleton的Instance *** 作访问一个Singleton的实例。

7、效果

单例模式有很多的优点:

  • 对唯一实例的受控访问

因为Singleton类封装它的唯一实例,所以它可以严格地控制客户怎样以及何时访问它。

  • 缩小名字空间

Singleton模式是对全局变量的一种改进,它避免了那些存储唯一实例的全局变量污染名字空间。

  • 允许对 *** 作和表示的精化

Singleton类可以有子类,而且用这个扩展类的实例来配置一个应用是很容易的。你可以用你所需要的类的实例在运行时配置应用。

  • 允许可变数目的实例

这个模式使得你易于改变你的想法,并允许Singleton类的多个实例。此外,你可以用相同的方法来控制应用所使用的实例的数目,只有允许访问Singleton实例的 *** 作而改变。

  • 比类 *** 作更灵活

另一种封装单件功能的方式是使用类 *** 作(即C++中的静态成员函数或者Smalltalk中的类方法)。但这两种语言技术都难以改变设计以允许一个类有多个实例。此外,C++中的静态成员函数不是虚函数,因此子类不能多态地重定义它们。

8、实现

单例模式的实现有一个演变改进的过程:

首先是饿汉式

public class Singleton {

    private static Singleton instance = new Singleton();

    private Singleton(){
    }

    public static Singleton getInstance(){
        return instance;
    }

}

首先我们要将代码修改为懒加载:

所以我们写为懒汉式:

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){
    }

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

}

下面我们来解决线程安全的问题:

首先我们会想到为访问点的getInstance方法加同步:

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){
    }

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

}

但是这样对于我们程序来说,效率比较低

这个时候就使用synchronized锁代码块来解决问题:

使用synchronized代码块有三种写法:

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){
    }

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

}

这种写法与将synchronized放到方法上一样,没有什么区别,都是没获取到锁的线程在阻塞。

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){
    }

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

}

这种写法确实是会提高效率,但是在多线程下还是不安全的,当线程A、B都到了竞争锁这段代码的时候,如果A获得锁,对Singleton进行实例化并结束后,B线程获取到锁,B没有判断现在是否有Singleton的实例,导致B线程继续实例化Singleton。

所以我们要在synchronized代码段内部再加一个判断,这也是第三种写法:

public class Singleton {

    private static Singleton instance = null;

    private Singleton(){
    }

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

}

这个代码就是双重判断的单例模式代码,这段代码也存在问题,当B线程执行第一个判断,线程A进入代码块执行实例化代码的时候发生了指令重排问题。B线程取得单例对象可能是不完整的,所以要防止单例对象指令重排,对静态变量instance加volatile。

public class Singleton {

    private static volatile Singleton instance = null;

    private Singleton(){
    }

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

}

指令重排是什么呢?指令重排为什么会导致B线程取得对象不完成呢?

JVM的指令重拍就是Java中简单的instance = new Singleton();

其实在JVM中的指令有以下三步:

1、memory = allocate();为对象分配内存空间

2、ctorInstance(memory);初始化对象

3、instance = memory 设置instance指向刚分配的内存地址

但是这些指令可能根据优化,会变为:

1---->3---->2这样的顺序,这样的话当线程A执行完1、3但是没有执行2的时候,instance已经没有指向null了,线程B抢占到CPU开始执行,他认为instance不为null,获取到了没有初始化的instance对象。

到此单例模式已经是线程安全的。

单例模式就讲到这里,下面我会先来讲创建性设计模式。

下一篇文章是工厂模式,欢迎大家前来捧场。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5697228.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存