设计模式--单例模式

单例模式(Singleton Pattern)属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

通常来说:new实例化会创建一个对象,new多个就会创建多个实例化对象。而有时候若是想不管new多少都只想创建一个实例化对象并使用,就可以使用单例模式创建对象。

单例模式分为饿汉式和懒汉式。

饿汉式 

饿汉式,形象的说法是饿的发慌,直接上来就开干。即类加载时就创建对象。

class Singleton{
    private static final Singleton singleton = new Singleton();

    private Singleton(){}

    public static Singleton getSingleton(){
        return singleton;
    }
}

通过上述代码可以看到:饿汉式单例模式使用private声明了构造方法,这样其他类就不能直接通过构造方法实例化该对象,而饿汉式单例模式通过暴露的get方法提供了一种访问其唯一的对象的方式。

饿汉式类加载时就创建了实例,这样可能会造成内存浪费的问题。可以使用登记式/静态内部类的方式实现Lazy Loading,这种方式只适用于静态域的情况。

public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE;  
    }  
}

这种方式利用了 类加载机制来保证初始化 instance 时只有一个线程,这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式就比较适合。

ps:如果静态属性/方法有 final 修饰时,则不会加载

懒汉式

 懒汉式顾名思义,太懒了,与饿汉式相反,等到需要使用的时候才创建对象。

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

 通过上述代码可以看到:懒汉式单例模式也使用private声明了构造方法,通过暴露的get方法提供了一种访问其唯一的对象的方式。不过懒汉式在get方法中多了一层判断,如果还没实例化那就实例化一个,若是已经实例化了,就返回该实例化对象。

饿汉式类加载时就创建了实例,这样可能会造成内存浪费的问题,而懒汉式虽然解决了这个问题,但是可能会造成线程不同步的问题。

如何解决呢?我们可以使用双检锁(DCL,double-checked locking),即:双重检验

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

synchronized也可以加在get方法上,但是这样会十分影响性能,一般不会这么用。 

如上述代码所示,DCL通过两重if判断解决线程不同步的问题,若是线程1,线程2都通过第一重检验 ,但只有第一个线程能拿到同步块的锁,当线程1拿到之后,线程2便被阻塞,直到线程1创建完成后释放锁。而后线程2拿到锁后,执行第二层检验,发现已经有实例后,无法通过第二层if判断。

双检锁第一层检验是为了完成实例化之后,之后的线程再不需要再执行synchronized代码块,提到效率;第二层检验是防止多次实例化。

执行instance = new Singleton();时会执行一下三个步骤:

  1. 在堆中开辟空间,分配内存
  2. 根据类加载的初始化顺序进行初始化
  3. 将内存地址返回给栈中的引用变量

 由于Java内存可能因为性能原因进行重排序从而导致问题。此时可以用到volatile关键字防止重排序。若是不使用volatile,由于Java内存模型允许无序写入,有些编译器因为性能原因可能会将上述的顺序2和3调换,从而出现instance已经指向了指定的内存空间但还未初始化,而其他的线程访问instance却还未初始化的问题。添加volatile关键字就相当于在其中加了一层内存屏障。

ps:JDK5 以及后续版本扩展了volatile语义,不再允许volatile写操作与其前面的读写操作重排序,也不允许volatile读操作与其后面的读写操作重排序

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Liu_Junwei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值