欢迎您访问365答案网,请分享给你的朋友!
生活常识 学习资料

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

时间:2023-06-30
这里写目录标题

单例模式

1、饿汉式

1.1 代码1.2 理解1.3 总结 2、懒汉式

2.1 代码2.2 理解2.3 总结 3、懒汉式+同步

3.1 代码3.2 理解3.3 总结 4、Double-Check

4.1 代码4.2 理解4.3 总结 5、Double-Check + volatile

5.1 代码5.2 理解5.3 总结 6.Holder方式

6.1 代码6.2 理解6.3 总结 7、枚举方式

7.1 代码7.2 理解7.3 拓展 单例模式

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

好处:

在某些类创建比较频繁,对于一些大型的对象,这是一笔很大的开销;省去了new操作符,降低了系统内存的使用频率,减轻GC压力;保证核心交易的服务器独立控制整个流程。 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进行保证程序的循序加载

注意点:

成员变量需要在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方式完全借助了类加载的特点:

当Sigleton5初始化过程中并不会创建Signleton5的实例Holder类中直接实例化singleton5所以当Holder被主动引用时才会创建singleton5的实例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高并发编程详解——多线程与架构设计》 汪文君

Copyright © 2016-2020 www.365daan.com All Rights Reserved. 365答案网 版权所有 备案号:

部分内容来自互联网,版权归原作者所有,如有冒犯请联系我们,我们将在三个工作时内妥善处理。