设计模式 - 单例模式

       设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

       使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

一、什么是单例模式(Singleton Pattern)  

        单例:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

        单例模式是一种常用的软件设计模式之一,其目的:保证某一个在整个应用中有且只有一个实例(一个类在内存只存在一个对象),即所有指向该类型实例的引用都指向同一块内存空间。

        Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例) -- 来自《设计模式之禅》

        比如:我们在系统启动时,需要加载一些公共的配置信息,对整个应用程序的整个生命周期中都可见且唯一,这时需要设计成单例模式。如:spring容器,session工厂,缓存,数据库连接池等等。

二、如何保证实例的唯一

        1)防止外部初始化

        2)由类本身进行实例化

        3)保证实例化一次

        4)对外提供获取实例的方法

        5)线程安全

写单例模式的步骤:

       1)在类内部里显示的把所有的构造方法都使用private修饰。

       2)在类内部构建一个自身对象。

       3)通过一个全局静态方法让外界访问到该对象。

三、几种单例模式方式

       Java 使用单例设计模式的方式有很多种,比如饿汉式,懒汉式,静态内部类式,双重检测锁式以及枚举式等。

1、饿汉式(线程安全)

     “因为饿,所以要立即吃饭,刻不容缓”,在定义类的静态私有变量的同时进行实例化。

public class MySingleton {
    private static final MySingleton instance = new MySingleton();

    private MySingleton() { }

    public static MySingleton getInstance() {
        return instance;
    }

    public void doXxx() {
        System.out.println("执行方法。。。");
    }
}

       好处:获取实例速度快 ,类加载即初始化实例,线程安全(static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题)。

       缺点:如果只是加载本类,而不是调用getInstance(),甚至永远没有调用,则会造成内存资源的浪费。

       即:线程安全,调用效率高 ,但是不能延迟加载

2、懒汉式(线程不安全)

      lazy load! 延时加载,懒加载!真正用的时候才加载。

public class MySingleton {
    private static MySingleton instance = null;

    private MySingleton() { }

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

     缺点:可能存在多个访问者同时访问,并构造了多个对象的问题。会造成线程不安全的问题,于是就有了下面加锁的实现。

3、双重检查加锁懒汉式(通常线程安全,低概率不安全)

双重检查加锁:

       可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响。

什么是“双重检查加锁”机制?

    是指:并不是每次进入getInstance()方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

  “双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

public class MySingleton {
    private volatile static MySingleton instance = null;

    private MySingleton() { }

    public static MySingleton getInstance() {
        // 第一重检查
        if (instance == null) {
            // 同步锁定代码块:此时同步对象是MySingleton.class
            synchronized (MySingleton.class){
                // 第二重检查
                if(instance == null){
                    instance = new MySingleton();
                }
            }
        }
        return instance;
    }
}

       注意:在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java5及以上的版本。

       提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,由于JVM底层内部模型原因,偶尔会出问题。不建议使用

4、静态内部类式(线程安全)

        外部类没有static属性,则不会像饿汉式那样立即加载对象。只有真正调用getInstance()时,才会加载静态内部类。加载类时是线程安全的。instance是 static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性兼备了并发高效调用和延迟加载的优势!

   《Effective Java》这本书中推荐的一种写法:

public class Singleton {
    private Singleton (){}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

写法非常巧妙(简直是神乎其技):

       对于内部类SingletonHolder,它是一个饿汉式的单例实现,在SingletonHolder初始化的时候会由ClassLoader来保证同步,使INSTANCE是一个真·单例。

        同时,由于SingletonHolder是一个内部类,只在外部类的Singleton的getInstance()中被使用,所以它被加载的时机也就是在getInstance()方法第一次被调用的时候。

       它利用了ClassLoader来保证了同步,同时又能让开发者控制类加载的时机。从内部看是一个饿汉式的单例,但是从外部看来,又的确是懒汉式的实现。

       好处:线程安全,资源利用率高,可以延时加载

5、枚举式(线程安全)

       枚举的思想其实是通过共有的静态 final 与为每个枚举常量导出实例的类,由于没有可访问的构造器,所以不能调用枚举常量的构造方法去生成对应的对象,因此在《Effective Java》 中,枚举类型为类型安全的枚举模式,枚举也被称为单例的泛型化。

《Effective Java》这本书中推荐的一种写法:

public enum SingletonDemo {
    INSTANCE;
    public void doXxx() {
        System.out.println("执行方法。。。");
    }
}

// 如果我们想调用它的方法时,仅需要以下操作:
public class DesignDemo {
    public static void main(String[] args) {
        SingletonDemo instance1 = SingletonDemo.INSTANCE;
        SingletonDemo instance2 = SingletonDemo.INSTANCE;
        System.out.println(instance1 == instance2); // true
        SingletonDemo.INSTANCE.doXxx();  // 执行方法。。。
    }
}

写法非常巧妙,解决了以下三个问题:

     (1)自由串行化。

     (2)保证只有一个实例。

     (3)线程安全。

优点:线程安全、调用效率高,并且可以天然的防止反射和反序列化漏洞!

缺点:它不能延时加载,在需要继承的场景(枚举类默认已隐式继承Enum类),它就不适用了。

四、该如何选择这些方式?

      单例对象占用资源少,不需要延时加载:枚举式 好于 饿汉式

      单例对象占用资源大,需要延时加载:静态内部类式 好于 懒汉式

 

ends ~

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值