【原创】自己动手实现牛逼的单例模式

其实写这篇文章之前,我犹豫了一下,毕竟单例大家都知道,写这么一篇文章会不会让人觉得老掉牙。后来想想,就当一种记录吧。先来一副漫画吧,如下图所示

image


ok,我们回顾下小灰的遭遇,上述漫画所提出的那些问题主要有以下三点:

为什么静态内部类的单例模式是最推荐的?

如何在反射的情况下保证单例?

如何在反序列化中保证单例?

针对上述三个问题有了这篇文章,以一种循序渐进的方式,引出最后一种单例设计模式,希望对大家能够有所帮助。

单例设计模式 1、饿汉式

这种其实大家都懂,不多说,上代码。

package singleton; public class Singleton1 { private static Singleton1 instance = new Singleton1(); private Singleton1 (){} public static Singleton1 getInstance() { return instance; } }

优点就是线程安全啦,缺点很明显,类加载的时候就实例化对象了,浪费空间。于是乎,就提出了懒汉式的单例模式

2、懒汉式 (1)懒汉式v1 package singleton; public class LazySingleton1 { private static LazySingleton1 instance; private LazySingleton1 (){} public static LazySingleton1 getInstance() { if (instance == null) { instance = new LazySingleton1(); } return instance; }

然而这一版线程是不安全的,于是乎为了线程安全,就在getInstance()方法上加synchronized修饰符,于是getInstance()方法如下所示

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

然而,将synchronized加在方法上性能大打折扣,于是乎又提出一种双重校验锁的单例设计模式,既保证了线程安全,又提高了性能。双重校验锁的getInstance()方法如下所示

public static LazySingleton1 getInstance() { if (instance == null) { synchronized (LazySingleton1.class) { if (instance == null) { instance = new LazySingleton1(); } } } return instance; } (2)懒汉式v2

懒汉式v1的最后一个双重校验锁版,不管性能再如何优越,还是使用了synchronized修饰符,既然使用了该修饰符,那么对性能多多少少都会造成一些影响,于是乎懒汉式v2版诞生。不过在讲该版之前,我们先来复习一下内部类的加载机制,代码如下

package test; public class OuterTest { static { System.out.println("load outer class..."); } // 静态内部类 static class StaticInnerTest { static { System.out.println("load static inner class..."); } static void staticInnerMethod() { System.out.println("static inner method..."); } } public static void main(String[] args) { OuterTest outerTest = new OuterTest(); // 此刻其内部类是否也会被加载? System.out.println("===========分割线==========="); OuterTest.StaticInnerTest.staticInnerMethod(); // 调用内部类的静态方法 } }

输出如下

load outer class... ===========分割线=========== load static inner class... static inner method

因此,我们有如下结论

加载一个类时,其内部类不会同时被加载。

一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。。

基于上述结论,我们有了懒汉式V2版,代码如下所示

package singleton; public class LazySingleton2 { private LazySingleton2() { } static class SingletonHolder { private static final LazySingleton2 instance = new LazySingleton2(); } public static LazySingleton2 getInstance() { return SingletonHolder.instance; } }

由于对象实例化是在内部类加载的时候构建的,因此该版是线程安全的。另外,在getInstance()方法中没有使用synchronized关键字,因此没有造成多余的性能损耗。当LazySingleton2类加载的时候,其静态内部类SingletonHolder并没有被加载,因此instance对象并没有构建。而我们在调用LazySingleton2.getInstance()方法时,内部类SingletonHolder被加载,此时单例对象才被构建。因此,这种写法节约空间,达到懒加载的目的,该版也是众多博客中的推荐版本。

ps:其实枚举单例模式也有类似的性能,但是因为可读性的原因,并不是最推荐的版本。

(3)懒汉式v3

然而,懒汉式v2版在反射的作用下,单例结构是会被破坏的,测试代码如下所示

package test; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import singleton.LazySingleton2; /** * @author zhengrongjun */ public class LazySingleton2Test { public static void main(String[] args) { //创建第一个实例 LazySingleton2 instance1 = LazySingleton2.getInstance(); //通过反射创建第二个实例 LazySingleton2 instance2 = null; try { Class<LazySingleton2> clazz = LazySingleton2.class; Constructor<LazySingleton2> cons = clazz.getDeclaredConstructor(); cons.setAccessible(true); instance2 = cons.newInstance(); } catch (Exception e) { e.printStackTrace(); } //检查两个实例的hash值 System.out.println("Instance 1 hash:" + instance1.hashCode()); System.out.println("Instance 2 hash:" + instance2.hashCode()); } }

输出如下

Instance 1 hash:1694819250 Instance 2 hash:1365202186

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zypjsw.html