一天一个设计模式:单例模式

概念:

  作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化,并向整个系统提供这个实例。

特点:

  1.单例类只能有一个实例

  2.单例类必须创建自己的唯一实例

  3.单例类必须给其他所有对象提供这一实例。

 

饿汉式单例类

public class EagerSingleton {
    private static EagerSingleton instance = new EagerSingleton();
    /**
     * 私有默认构造子
     */
    private EagerSingleton(){}
    /**
     * 静态工厂方法
     */
    public static EagerSingleton getInstance(){
        return instance;
    }
}

由于选用的是静态资源,在类加载的时候,静态变量instance就会被初始化,类的唯一变量也在这时候创建了出来。

饿汉式,顾名思义,急于创建对象,在类加载的时候就完成了对象的创建。

饿汉式是一种采用“空间换取时间”,当类装载的时候就会创建类的实例,不管你用不用,先创建,调用的时候,无需判断直接使用,节约了时间。

 

懒汉式单例

public class LazySingleton {
    private static LazySingleton instance = null;
    /**
     * 私有默认构造子
     */
    private LazySingleton(){}
    /**
     * 静态工厂方法
     */
    public static synchronized LazySingleton getInstance(){
        if(instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }
}

只有在真正需要的时候才会去创建,采用的是时间换空间,由于是线程安全的,会降低访问速度。固可使用双检锁的方式进行优化。

 

双重检查加锁

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

双检锁的关键是,使用volatile,它在此处的作用是,该变量将不会被某一个线程缓存,而是在共享内存中,被所有进行读写的内存共享到,从而保证多个线程能够正确处理该变量。

public class Singleton {
    private volatile static Singleton instance = null;
    private Singleton(){}
    public static Singleton getInstance(){
        //先检查实例是否存在,如果不存在才进入下面的同步块
        if(instance == null){
            //同步块,线程安全的创建实例
            synchronized (Singleton.class) {
                //再次检查实例是否存在,如果不存在才真正的创建实例
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

注:由于volatile关键字会屏蔽虚拟机的一些代码优化,所以运行效率并不高,所有其实应该尽量避免使用双检锁的方式来实现单例。

 

懒加载模式(内部类形式)

什么是内部类:

  内部类就是指在类里面的类,被static修饰的内部类,称为类级内部类,无static修饰的内部类称为对象级内部类。

  类级内部类是外部类的static部分,它的对象与外部类对象间并没有依赖关系,因此可以直接创建。而对象级内部类的实例,是绑定在外部对象的实例中的。

  类级内部类中,可以定义静态方法,在静态方法中只能够引用外部类中的静态成员或者成员变量。

  类级内部类相当于外部类的成员,只有在第一次被使用的时候才会加载。

多线程缺省同步锁的相关知识:

  在下面操作下,jvm会自动为该操作进行同步,以避免出现并发安全问题。

  1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据。

  2.访问final字段时,

  3.在创建线程前创建对象时,

  4.线程可以看见它将要处理的对象时。

综上的解决思路:

  采用静态初始化器的方式通过jvm来保证线程的安全性,采用类级内部类的方式实现延迟加载。

public class Singleton {
    
    private Singleton(){}
    /**
     *    类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
     *    没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
     */
    private static class SingletonHolder{
        /**
         * 静态初始化器,由JVM来保证线程安全
         */
        private static Singleton instance = new Singleton();
    }
    
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }
}

  在getInstance第一次被调用时,会读取内部类的instance,这时,类级内部类完成初始化,从而创建单例,实现了延时加载,由于是静态成员,只有在类加载的时候才加载一次,且通过jvm的缺省方式实现了线程安全问题。

 

单例类枚举:

  单元素的枚举类型已经成为实现Singleton的最佳方法。用枚举来实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可。

public enum Singleton {
    /**
     * 定义一个枚举的元素,它就代表了Singleton的一个实例。
     */
    
    uniqueInstance;
    
    /**
     * 单例可以有自己的操作
     */
    public void singletonOperation(){
        //功能处理
    }
}

  使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

posted @ 2018-10-31 15:42  萌新啊萌新是我  阅读(187)  评论(0编辑  收藏  举报