单例模式的几种实现方式

单例模式是java中最简单的设计模式之一,他有几种要求

  1. 单例类里面只允许有一个单例
  2. 单例类必须自己创建自己的单例,即需要让构造器私有化
  3. 单例类必须可以给其他对象提供自己的实例

我们要知道设计模式都是围绕着七种设计原则:单一职责原则,接口隔离原则,里式替换原则,开闭原则,依赖倒置原则,迪米特法则(demeter)以及合成复用原则。

下面谈一谈单例模式的几种实现方法:

饿汉模式

饿汉模式是很常见的一种模式,比如jdk的runtime类就是用的饿汉模式,他的思想是在最开始
便创建好自己的实例化对象,谁来取,就直接给谁用,这种方式是绝对线程安全的,实现的
代码也很简单:
public class Hungry {

    private static final Hungry instance = new Hungry();

    private Hungry(){}

    public static Hungry getInstance(){
        return instance;
    }
}

当然,饿汉模式不只这一种写法,还可以把对象的实例化交给静态代码块,因为静态代码块在类加载的时候是要先加载的,所以符合饿汉模式的设计要求,代码如下:

public class Hungry2 {

    private static final Hungry2 instance;

    //跟第一种类似,只不过是把对象的实例化放在了静态代码块里面
    static {
        instance = new Hungry2();
    }

    private Hungry2() {
    }

    public static Hungry2 getInstance() {
        return instance;
    }
}

但是饿汉模式可能会造成资源的浪费,因为你有可能从始至终都没有用到我的实例对象,所以有第二种模式。

懒汉模式

懒汉模式的设计思想是什么时候用,我什么时候再给你创建,即懒加载机制(lazy-loading),这样可以保证我们创建的对象一定会被用到,但是这种机制会导致线程的不安全,我们先看代码:

public class LazySingleTon {

    private  LazySingleTon(){}

    private static LazySingleTon instance;

    public static LazySingleTon getInstance(){
        //当对象为空的时候才去实例化,但是有可能多个线程同时进入if语句,创建出多个对象
        if(instance == null){
            instance = new LazySingleTon();
        }
        return instance;
    }
}

缺点很明显,在多线程同时调用getInstance()的时候,可能都进入了if判断,但是第一个进来的线程还没来得及创建对象,所以可能会创建多个对象。

那么要实现线程安全,最简单的方式就是加锁:

public class LazySingleTon2 {

    private LazySingleTon2(){}

    private static LazySingleTon2 instance;

    public static synchronized LazySingleTon2 getInstance(){
        //相对于懒汉模式,方法加入了synchronized来修饰,保证线程安全,但是效率会大大降低
        if(instance == null){
            instance = new LazySingleTon2();
        }
        return instance;
    }
}

加入同步锁之后可以保证同一时间只能有一个线程调用getInstance()方法,保证了线程安全,但缺点也很明显——太慢了!于是有人又想出了一种方法:不用同步锁修饰方法,而是修饰一个代码快:

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

乍一看感觉好强,但是仔细一想其实没卵用,因为这种方式根本就不能阻止线程去new对象,线程只是进入if语句之后被阻塞了,哪怕它等待了一万年,在它开始执行的时候还是会去创建对象。

但是在这个代码的基础上再稍微修改一点就可以用了:

public class DoubleCheck {

    private static volatile DoubleCheck instance;

    private DoubleCheck() {
    }

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

意思就是在代码块里面再加一个判断,如果别的线程已经创建了对象,后面等待的所有线程都不会再去实例化对象,这种方式叫做 双重检查锁 单例模式(懒汉模式,线程安全)

静态内部类

静态内部类在类加载的时候并不会初始化,而是在调用的时候才会,这很符合懒加载机制,而且静态内部类初始化的时候是绝对线程安全的。所以这种方式推荐大家使用。

public class StaticInnerClass {
    //类加载时,静态内部类不会加载,当调用的时候才会初始化
    private static class singleTonHolder{
        private static final StaticInnerClass instance = new StaticInnerClass();
    }
    //构造器私有化,防止new对象
    private StaticInnerClass(){}

    public static StaticInnerClass getInstance(){
        return singleTonHolder.instance;
    }
}

枚举类

最后一种,枚举类,枚举类型我们平时很少用到,但是用它来实现单例模式很简单,先看代码:

public enum EnumSingle {
    ENUM_SINGLE;
    public static EnumSingle getInstance(){
        return ENUM_SINGLE;
    }
}

枚举类的会自动对常量添加 public static final修饰,并且enum类本身也是final的,而用枚举实现单例模式有三个特性:自由序列化,线程安全,保证单例。

enum有且仅有private的构造器,防止外部的额外构造,这恰好和单例模式吻合,也为保证单例性做了一个铺垫。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值