JAVA设计模式之单例模式

前言

单例模式是java日常开发中采用比较多的一种设计模式,其主要目的为避免对象实例多次创建,其优点是节省系统资源,减少内存开支。单例模式也存在不足之处,单例模式扩展困难。

单例模式一般有两种类型,即懒汉和饿汉模式。下面来举例说明这两种方式的实现。

1.饿汉单例模式

饿汉模式,从字面含义形象地阐述了这种模式下,实例对象的加载方式:即类加载同时进行实例化对象。

public class Singleton_hungry {

    /** 定义实例变量 采用饿汉模式 **/
    private static final Singleton_hungry singleton = new Singleton_hungry();

    /** 私有化构造器 **/
    private Singleton_hungry(){

    }

    /** 使用静态工厂方法,提供获取单例方法 **/
    public static Singleton_hungry getInstance() {
        return  singleton;
    }
}

随着类Singleton_hungry加载同时,对象singleton也会被实例化。通过私有化构造器以及对外提供静态工厂方法保证了创建实例的限制及唯一的获取方式。由于在类加载的同时也会加载实例对象,饿汉模式初始加载时会消耗更多的内存资源,降低系统加载速度。

2.懒汉单例模式

区别于饿汉单例模式,懒汉模式则不会在类加载同时创建实例对象,只有在第一次使用该对象时才会将对象加载。在考虑多线程情况下,饿汉模式是在类加载时进行对象实例化,此时不存在线程安全问题。而懒汉模式则需要考虑线程安全问题

(1)单线程

public class Singleton {


    /** 定义实例变量 采用懒汉模式 **/
    private static Singleton singleton;

    /** 私有化构造器 **/
    private Singleton(){

    }

    /** 使用静态工厂方法,提供获取单例方法 **/
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return  singleton;
    }

在不考虑线程安全情况下,我们只需要在第一次获取对象时进行判空处理。

(2)多线程情况下

由于在多线程情况下,可能会出现线程1进行singleton判空处理时,此时线程2也在进行singleton判空处理时,这时就会出现singleton被实例化两次的情形,这与我们的单例的目的相悖,为了避免这种情形的发生,我们可以采用对getInstance()方法进行同步加锁

public class Singleton {


    /** 定义实例变量 采用懒汉模式 **/
    private static Singleton singleton;

    /** 私有化构造器 **/
    private Singleton(){

    }

    /** 使用静态工厂方法,提供获取单例方法,采用加锁同步 **/
    public synchronized static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return  singleton;
    }
}

但是这种方式存在缺点,对getInstance()加锁后,每次调用该方法都会进行加锁处理,会额外消耗性能。由此进行优化如下

public class Singleton {


    /** 定义实例变量 采用懒汉模式 volatile防止指令重排序 **/
    private volatile static Singleton singleton;

    /** 私有化构造器 **/
    private Singleton(){

    }

    /** 使用静态工厂方法,提供获取单例方法,采用双重校验锁方式 **/
    public  static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return  singleton;
    }
}

优化后,对第一次获取该对象时才会进行同步加锁处理,大大减少了加锁带来的资源消耗。当线程1调用getInstance方法,经过第一个判空准备继续执行时,发现资源被线程2占用,此时线程1等待。线程2在获取资源时,进行第二次判空处理后创建实例,执行结束,释放资源。此时线程1获取到资源,执行第二次判空处理,发现实例已存在,随即执行结束。对于创建实例对象过程中存在的指令重排序问题,我们需要使用volatile关键字对待实例化对象进行修饰,这是因为在实例化对象即singleton = new Singleton()过程中,JVM执行了3步指令

1.给singleton对象分配内存 2.调用Singleton的构造函数,初始化形成实例 3.将singleton对象指向对应内存空间

单线程情形下,执行顺序无论是1->3->2还是1->2->3,获取到实例结果相同但是在多线程情形下,在线程1,2执行时可能会出现线程1执行1->3->2顺序时在执行到第3步分配内存空间时,线程2执行判空处理,发现当前instance不为空,直接返回instance,但是此时的instance并没进行第2步进行初始化对象,从而出现错误,所以使用volatile关键字对实例化过程进行限制,避免出现未完成实例化过程时,有线程对instance对象进行读操作(此处为instance判空处理)

由此可见,双重加锁检查保证线程安全同时,最大化地利用了系统资源。

在实现单例的情形中,除上述实现方式,还可以使用另外两种单例实现方式即静态内部类和枚举

3.静态内部类

由于在类加载过程中,外部类加载时内部类并不会随着外部类加载而加载,只有在调用内部类方法或者属性时才会进行内部类的加载。由此可以借由内部类实现单例模式

public class Singleton {

    /** 私有化构造器 **/
    private Singleton(){

    }

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

    }

    /** 使用静态工厂方法,提供获取单例方法 **/
    public  static Singleton getInstance() {
        return  InnerClass.instance;
    }
}

4.枚举

由于枚举只会被加载一次而且是线程安全的,所以还可以采用枚举实现单例模式,注意到枚举类的构造器默认且必须private修饰

public class Singleton {

    /** 私有化构造器 **/
    private Singleton(){

    }

    private enum SingletonEnum{
        INSTANCE;

        private final Singleton instance;

        SingletonEnum(){
            instance = new Singleton();
        }

        private Singleton getInstance(){
            return instance;
        }
    }
    /** 使用静态工厂方法,提供获取单例方法 **/
    public  static Singleton getInstance() {
        return  SingletonEnum.INSTANCE.getInstance();
    }
}

结束语

以上就是对单例模式中的阐述,如有错误,请不吝指教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值