关于单例模式的一些讨论(实现单例模式)

对于如何实现单例模式,已经有很多博客来说明这个事了,比如陈皓老师的深入浅出单实例Singleton设计模式,想要深入了解单例模式可以直接去看他们博客,这里自我讨论学习一下。

在陈浩老师的博客中提到了两个存在线程安全的单例模式实现方式,在《Java多线程编程核心技术》也提到了这些问题,如何正确地写出单利模式这篇博客也提到了这些方法。之所以给出这么多连接,是希望以后自己忘了的时候可以随时查查。

在这里,直接讨论几种正确的方法。

1、通常理解的饿汉模式(立即加载)

public class Singleton{
    private volatile static Singleton singleton = new Singleton();
    private Singleton(){}  //私有构造函数
    public static Singleton getInstance(){
        return singleton;
    }
}

这种方式的缺点在于 将类的创建委托给了类加载器,我们无法控制,比如我们需要在创建类以前需要干一些其他事情(比如某个配置文件,或是某个被其他类创建的资源),这些事情依赖其他类。我们希望在第一次调用getInstance()的时候才创建类。
因此对上面方法进行修改。老版《Effective Java》中推荐的做法是:

public class Singleton{
    private static class singletonHolder{ //交由另一个私有函数来创建
        private static final Singleton INSTANCE = new Singleton();
    }
    private Singleton(){}
    public static final Singleton getInstance(){
        return singletonHolder.INSTANCE;
    }
}

2、双重检查模式

public class Singleton{
    private volatile static Singleton singleton = null;
    private Singletion(){}
    public static Singleton getInstance(){
        if(singleton==null){          //第一个singleton==null判断
            synchronized(Singleton.class){
                if(singleton==null){  //第二个singleton==null判断
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

如果去掉第一个判断,类似于在getInstance方法上加上synchronized关键字,每次获取一个实例都要获取锁,消耗太大。
如果去掉第二个判断,有可能两个线程同时通过了检查,从而同步地创造出了两个实例。

使用 volatile 有两个功用:
1)这个变量不会在多个线程中存在复本,直接从内存读取。
2)这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。

使用volatile 主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
1)给 singleton 分配内存
2)调用 Singleton 的构造函数来初始化成员变量,形成实例
3)将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了)

另外synchronized(Singleton.class)是获取Singleton的类锁(区别于“对象锁”),另一种获取类锁的方式是为static静态方法加上synchronized关键字。使用类锁后,所有被synchronized修饰的static方法(代码块)只能被同步执行。
另外类锁和对象锁之间并不冲突。

3、枚举单例模式

public enum Singleton{
    INSTANCE;

    int attr;     //属性示例
    Singleton(){ //默认私有的构造方法。
    }
}

在使用枚举单例模式时,直接使用Singleton.INSTANCE类访问。

关于使用枚举简单、而且是线程安全的等等,很少有人解释清楚,可以参考这个单例模式中为什么用枚举更好

在《Effective java》中认为,枚举天生就是不可变的,因此所有的域都应该是final的。它们可以是公有的,但最好是私有的。常量是线程安全的,不能改嘛。另外,在一开始,书中说“枚举类型是指由一组固定的常量组成合法值的类型”

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值