单例模式

一、定义

在《设计模式之禅》中对单例模式的描述如下:

单例模式是一个比较简单的模式,定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

单例模式通用代码

public class Singleton {

        private static final Singleton singleton = new Singleton();

        // 限制产生多个对象,声明构造函数为私有

        private Singleton() { }

优点:

1、由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显;

2、由于单例模式只生成一个实例,所以减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决(在JavaEE中采用单例模式时需要注意JVM垃圾回收机制);

3、单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。

4、单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理。

缺点:

1、单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。

2、单例模式对测试是不利的。在并行开发环境中,如果单例模式没有完成,是不能进行测试的,没有接口也不能使用mock的方式虚拟一个对象。

3、单例模式与单一职责原则有冲突。一个类应该只实现一个逻辑,而不关心它是否是单例的,是不是要单例取决于环境,单例模式把“要单例”和业务逻辑融合在一个类中。



二、代码实现

1、不带参数的构造函数单例实现

在上面的说明中单例模式的通用实现写法不会出现并发问题,但是它是饿汉式的,在ClassLoader加载类后Kerrigan的实例就会第一时间被创建,饿汉式的创建方式在一些场景中将无法使用:譬如实例的创建是依赖参数或者配置文件的,在getInstance()之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

对于实例化单例对象不用带任何参数的时候最好的实现方式(不考虑对象序列化)如下:


public class Singleton{

        private static class SingletonHolder {

                static final Singleton mInstance = new Singleton();

        }

        public static Singleton getInstance() {

                return SingletonHolder.mInstance;

        }

        private Singleton(){}

}


这种写法使用JVM本身机制保证了线程安全问题;由于SingletonHolder是私有的,除了getInstance()之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷,也不依赖JDK版本;


2、带参数的构造函数单例实现


public class Singleton {

        private static volatile Singleton mInstance = null;

        public static Singleton getInstance(Context pContext) {

                if(mInstance == null) {//双重检查,避免进入同步块造成多余性能损耗;

                        synchronized(Singleton.class) {

                                if(mInstance == null){

                                        mInstance = new Singleton(pContext);

                                }

                        }

                }

               return mInstance;

        }

       private Singleton (Context pContext) { }

}

这段代码被编译后在JVM执行的对应汇编代码大致做了三件事:

1、给对象的实例分配内存;

2、初始化对象的构造器;

3、将mInstance对象指向分配的内存空间(mInstance非null)

由于Java编译器允许处理器乱序执行,以及JDK1.5之前JMM中Cache、寄存器到主内存回写顺序的规定,以上第二点和第三点的顺序是无法保证的,也就是执行顺序可能是1-2-3或者是1-3-2,如果是后者则会导致空指针异常。所以用volatile声明单例对象,保证多线程操作时变量的可见性;


更多单例模式实现参考:点击打开链接


3、 volatile的使用

volatile:用来确保将变量的更新操作通知到其他线程,当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其它内存操作一起重新排序。volatile变量不会被缓存在寄存器或者对其它处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。但是volatile操作不是原子操作,原子操作的意思就是这条语句要么就被执行完,要么就没有被执行过,不能出现执行了一半这种情形。

Java对象中声明为volatile的字段常用于线程之间状态信息的同步。使用volatile也会带来一些副作用,它会限制现代JVM的JIT编译器对这个字段的优化,比如volatile字段必须遵守一定的指令顺序。间言之,volatile字段值在应用程序的所有线程和CPU缓存中必须保持同步。为了确保CPU缓存及时更新,即在各个线程之间保持同步,出现volatile字段的地方都会加入一条CPU指令:内存屏障(通常称为member或fence),一旦volatile字段值发生变化就会触发CPU缓存更新。

频繁更新,改变或写入volatile字段有可能导致性能问题(读区volatile字段不会造成性能问题),就不大容易碰到这类问题。

volatile变量修饰符如果使用恰当的话,它比synchronized 的使用和执行成本会更低,因为它不会引起线程上下文的切换和调度。







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值