并发编程——设计模式之单例模式

单例模式

单例模式(Singleton)是一种常见的设计模式。在java应用中,单例对象能够保证在一个JVM中,该对象只有一个实例存在;

好处:

  1. 在某些类创建比较频繁,对于一些大型的对象,这是一笔很大的开销;
  2. 省去了new操作符,降低了系统内存的使用频率,减轻GC压力;
  3. 保证核心交易的服务器独立控制整个流程。

1. 饿汉式

1.1 代码

public final class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton(){}

    public static Singleton getInstance() {
        return singleton;
    }

}

1.2 理解

饿汉式的关键在于singleton作为类变量直接得到初始化,也就是当我们使用Singleton类的时候,singleton实例直接完成创建,如果该类中存在其他变量,也会直接完成创建。

singleton作为类变量在类初始化的过程中会被手机()方法中,该方法能够百分之百保证同步,也就是singleton能够在多线程的情况下保证实例的唯一性,而不会被创建两次,但是singleton被ClassLoader加载后可能很长一段时间才被使用,那就意味着singleton实例所开辟的堆内存会驻留更久的时间。

如果一个类中的成员属性较少,且占用的内存资源不多,饿汉式方法可以使用,但是,如果一个类成员属性较多并且包含比较多的资源,这种方式则不太适用。

1.3 总结

饿汉式的单例设计模式可以保证多个线程下唯一实例,getInstance方法性能比较高,但是无法懒加载。

2. 懒汉式

2.1 代码


public final class Singleton1 {

    private static Singleton1 singleton = null;

    private Singleton1(){}

    public static Singleton1 getInstance() {
        if (singleton == null) {
            singleton = new Singleton1();
        }
        return singleton;
    }

}

2.2 理解

懒汉式就是在使用类实例的时候再去创建(用时创建),这样就避免类在初始化时提前创建。

当类初始化的时候,signleton并不会被初始化,在调用getInstance方法的时候,会判断singleton是否为空,如果不为空,则进行实例化。当然这种情况在单线程环境中是不会有什么问题的,但是当getInstance方法在多线程的情况下,则会导致singleton被实例化多次的情况,不能够保证单例的唯一性。

2.3 总结

懒汉式的单例设计模式:顾名思义,能够实现懒加载,但是不能够保证多线程环境的实例唯一。

3. 懒汉式+同步

3.1 代码

public final class Singleton2 {

    private static Singleton2 singleton = null;

    private Singleton2(){}

    public static synchronized Singleton2 getInstance() {
        if (singleton == null) {
            singleton = new Singleton2();
        }
        return singleton;
    }

}

3.2 理解

懒汉式的方法可以保证实例的懒加载,但无法保证实例的唯一性,在多线程的情况下,singleton又被称为共享资源(数据),在多线程对其访问的时,需要保证数据的同步。

在getInstance()方法中添加synchronized关键字,保证只有一个线程能够进入,保证多线程的情况下,能够保证实例的唯一性,但是synchronized关键字的排他性,导致getInstance()方法在同一时刻只能被一个线程访问,性能低下。

3.3 总结

懒汉式+同步synchronized方法,能够保证实例的唯一性,但是synchronized的排他性,导致性能低下。

4. Double-Check

4.1 代码

public final class Singleton3 {

    private static Singleton3 singleton = null;

    private Singleton3(){}

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

}

4.2 理解

Double-Check提供了一种高效的数据同步策略,那就是首次初始化的时候加锁,之后则允许多线程同时进行方法的调用访问。

这种方法能够满足懒加载,又保证了实例的唯一性,同时掘弃了synchronized加载方法上造成的效率低下,但是这个方法还是存在一定的问题,如上述代码不会存在什么问题,但是当类Singleton中存在其他成员变量的时候,可能由于重排序的问题导致空指针的出现。

空指针:
当Singleton中存在其他成员变量需要初始化,根据JVM运行时指令的重排序和Happens-before原则,若是singleton被先初始化,则其余成员变量未被实现,若此时调用,则会导致空指针异常

4.3 总结

Double-Check,高效的数据同步策略,实现了懒加载,保证了实例的唯一性,掘弃了synchronized加载方法上造成的效率低下,但是当类中存在多个成员变量的时候,可能出现空指针的问题。

5. Double-Check + volatile

5.1 代码

public final class Singleton4 {

    private Connection connection;

    private volatile static Singleton4 singleton = null;

    private Singleton4(){
        //省略具体实现方法,过多,举个栗子多变量的情况
        this.connection = new Connection(){};
    }

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

}

5.2 理解

在Double-Check中,说明如果多变量情况下可能出现空指针的情况,使用volatile进行保证程序的循序加载

注意点:

  1. 成员变量需要在signleton之前

5.3 总结

Double-Check+valatile能够保证懒加载、多线程单例且获取高效,且不会发生重排序导致的空指针问题。

6.Holder方式

6.1 代码

public final class Singleton5 {

    private Singleton5(){}

    private static class Holder {
        private static Singleton5 singleton5 = new Singleton5();
    }

    public static Singleton5 getInstance() {
        return Holder.singleton5;
    }

}

6.2 理解

Holder方式完全借助了类加载的特点:

  1. 当Sigleton5初始化过程中并不会创建Signleton5的实例
  2. Holder类中直接实例化singleton5
  3. 所以当Holder被主动引用时才会创建singleton5的实例
  4. Singleton5的实例创建过程在java程序编译时期收集至()方法中,该方法是同步方法,保证内存可见性、JVM指令的顺序性和原子性。

Holder方式是单例设计中目前最好的设计之一。

6.3 总结

Holder方法,能够保证懒加载,保证实例的唯一性,同事因为是类初始化加载所以能保证内存的可见性以及JVM指令的有序性和原子性。

7. 枚举方式

7.1 代码

public enum Singleton6 {

    /**
     * 单例
     */
    INSTANCE;

    Singleton6(){
        System.out.println("Singleton6 初始化!");
    }

    public static Singleton6 getInstance() {
        return INSTANCE;
    }

    public static void method(){}
}

7.2 理解

枚举方式,因为枚举类型本身不允许被继承,同时是线程安全的且只能被实例化一次,但是枚举不能够实现懒加载,调用静态方法立即会被实例化,上诉代码中增加了静态方法,可以实验一下。

7.3 拓展

枚举+holder方法实现懒加载,代码如下:

public class Singleton7 {

    private Singleton7(){}

    
    private enum HolderEnum {
        /**
         * 枚举单例
         */
        INSTANCE;
        private Singleton7 singleton7;

        HolderEnum() {
            this.singleton7 = new Singleton7();
        }

        private Singleton7 getSingleton7(){
            return singleton7;
        }
    }

    public static Singleton7 getInstance() {
        return HolderEnum.INSTANCE.getSingleton7();
    }

}

拓展:在类中增加Holder枚举的方式,实现枚举的懒加载。

学习自:
《Java高并发编程详解——多线程与架构设计》 汪文君

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值