单例模式,附代码(加了double check中volatile作用)

转载自自己的博客 https://www.cnblogs.com/zhaokexin/p/9968078.html

大家好,博主是个东北人哈尔滨那嘎达的,在实习中吧,感觉自己学习之路贼拉长,学习又没乐趣,感觉工作中的人都感觉我都东北话好玩儿,

那我就用东北话来分享一些知识和学习成果,这样既进步又有趣,以下有什么不对的欢迎大家呲哒我。

在我们写代码的时候总会遇到抛异常啥的,但是那玩意究竟咋用,往哪嘎达用,我就简简单单分享一下子。

单例设计模式的7种写法

咱们在面试中面试官总会问我们会什么设计模式,一般我们会说几种常见的常用的设计模式:工厂方法模式、单例模式、建造者模式、原型模式

代理........一大堆

其实说的多不如会的精几个,下面我说一下我对单例的了解,也欢迎大家指正

首先我们知道单例分为懒汉和饿汉,

第一种(懒汉,线程不安全)不建议用:

复制代码

public class Singleton{
      private static Singleton instance;
      private Singleton(){}

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

复制代码

懒汉,一看我们就能知道字面意思很懒,人家调用getInstance才实例化,但是如果多线程就不安全了,当多个线程同时调用getInstance时候,可能两条线程同时判断是null

那么就可能出两个或者两个以上的实例,就违背了单例的原则,那么我们怎么能让懒汉也安全呢?

第二种(懒汉,线程安全)同步方法不建议用:

复制代码

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

复制代码

我们可以看出我们加了synchronized同步方法来控制线程安全,当一条线程执行getInstance()其他线程被阻塞,所以保证一个时刻只有一个线程访问,

但是带来致命缺点就是会效率太低了。

第三种(饿汉,线程安全)

复制代码

//对象是方法被调用时,才初始化,也叫做对象的延时加载。称为:懒汉式。
public class Singleton{
   private static Singleton instance = new Singleton();
   private Singleton () { }
   public static Singleton getInstance() {
       return instance;
   }
}

复制代码

代码可以看出,讲构造函数私有化,并且在类中创建一个本类对象,在类中写一个方法获取到本类对象。

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到懒加载的效果。如果从始至终从未使用过这个实例的话,则会造成内存的浪费。

第四种静态代码块(饿汉,线程安全)

复制代码

public class Singleton {

    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    private Singleton() {}

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

复制代码

这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,

就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。

第五种双重检查(可以用)

复制代码

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

复制代码

Double-Check概念对于多线程开发来说不会陌生,就像代码中一张,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。有人问怎么保证安全的呢?其实我们实例化代码执行一次后,后面再次访问时,判断第一个if (singleton == null)的时候因为不是null,所以不用走同步的 synchronized (Singleton.class) 直接return实例化对象。就可以了

(2019/3/1增加volatile作用)

volatile是为了防止指令重排,而不是为了可见性,为什么要保证Singleton 变量的指令重排呢?

我们两个线程thread1和thread2同时调用getInstance()中的singleton = new Singleton()不是一个原子操作,所以对于cpu层面会有三次操作这一行代码,所以会这三次操作:

  1. 给 singleton 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量,形成实例
  3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null 了

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

这里的关键在于——线程thread1对instance的写操作没有完成,线程thread2就执行了读操作

 

这种方法线程安全;延迟加载;效率较高。

第六种静态内部类(可以用)

复制代码

public class Singleton {

    private Singleton() {}

    private static class SingletonInstance {
        private static final Singleton instance = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonInstance.instance ;
    }
}

复制代码

其实这种和饿汉式有点一样又不咋像,都是用类装载的机制来保证初始化实例时只有一个线程,但是饿汉式加载就实例化了,没有懒加载,这种静态内部类就是在类加载不回实例化

需要实例的时候调用getInstance,然后为我们返回一个内部类实例的Singleton。

忘记说第七种了,其实就是一种懒汉,像第二种同步方法,这种就同步代码块,都是效率低,不建议使用的。

打卡2018/11/16   博主是个实习的小白,每天积累一点,欢迎大家指正评论,一起加油!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值