单例模式

    xiaoxiao2025-04-20  5

    

    设计模式(Design Pattern)

    是一套被反复使用,多数人知晓的,经过分类编目的代码设计经验的总结。 目的使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码可靠性。 基本模式分类:基本模式分类有23种。

    1.创建型模式

    对象的创建会消耗掉系统的很多资源,所以单独对对象的创建进行研究,从而能够高效地创建对象就是创建型模式要探讨的问题。这里有6个具体的创建型模式可供研究,它们分别是:

    简单工厂模式(Simple Factory); 工厂方法模式(Factory Method); 抽象工厂模式(Abstract Factory); 创建者模式(Builder); 原型模式(Prototype); 单例模式(Singleton)。

    说明:严格来说,简单工厂模式不是GoF总结出来的23种设计模式之一。

    2.结构型模式

    在解决了对象的创建问题之后,对象的组成以及对象之间的依赖关系就成了开发人员关注的焦点,因为如何设计对象的结构、继承和依赖关系会影响到后续程序的维护性、代码的健壮性、耦合性等。对象结构的设计很容易体现出设计人员水平的高低,这里有7个具体的结构型模式可供研究,它们分别是:

    外观模式(Facade); 适配器模式(Adapter); 代理模式(Proxy); 装饰模式(Decorator); 桥模式(Bridge); 组合模式(Composite); 享元模式(Flyweight)。

    3.行为型模式

    在对象的结构和对象的创建问题都解决了之后,就剩下对象的行为问题了,如果对象的行为设计的好,那么对象的行为就会更清晰,它们之间的协作效率就会提高,这里有11个具体的行为型模式可供研究,它们分别是:

    模板方法模式(Template Method); 观察者模式(Observer); 状态模式(State); 策略模式(Strategy); 职责链模式(Chain of Responsibility); 命令模式(Command); 访问者模式(Visitor); 调停者模式(Mediator); 备忘录模式(Memento); 迭代器模式(Iterator); 解释器模式(Interpreter)。

    今天开始设计模式的学习总结之旅,第一站——单例模式。

    单例模式介绍

    有些对象我们只需要一个,比如:配置文件,工具类,线程池,缓存,日志对象等,如果创造出多个实例,就会出现问题,通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源,所以单例模式便应运而生。

    单例模式定义

    确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

    使用场景

    确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。如要访问IO和数据库等资源,这时候要考虑使用单例模式。

    UML类图

    角色介绍: 1. Client——高层客户端。 2. Singleton——单例类。 实现关键点: 1.构造函数不对外开放,一般为Private. 2. 通过一个静态方法或者枚举返回单例类对象。 3. 确保单例类的对象有且只有一个,尤其是在多线程环境下, 4. 确保单例类对象在反序列化时不会重新构建对象。

    通过将单例类的构造函数私有化,使得客户端代码不能通过new的形式手动构造单例类的对象。单例类会暴露一个公有的静态方法,客户端需要调用这个静态方法获取到单例类的唯一对象。在获取这个单例对象的过程中需要确保线程安全,即使在多线程环境下构造单例类的对象也是有且只有一个。

    实现方式

    饿汉模式

    在类加载的同时,实例就被创建。

    <code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> Singleton { <span class="hljs-comment">/* * 饿汉型 */</span> <span class="hljs-comment">// 构造方法私有化,不允许外界外部直接创建对象</span> <span class="hljs-keyword">private</span> <span class="hljs-title">Singleton</span>() { } <span class="hljs-comment">// 创建类的唯一实例,使用private static 修饰</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Singleton instance = <span class="hljs-keyword">new</span> Singleton(); <span class="hljs-comment">// 提供一个用于获取实例的方法,使用public static 修饰</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title">getInstance</span>() { <span class="hljs-keyword">return</span> instance; } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li></ul>

    懒汉模式

    在需要获取实例的时候,实例才被创建,在类的生命周期内仅仅会被创建一次(注意static的修饰)。

    <code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Singleton2</span> {</span> <span class="hljs-javadoc">/** * 懒汉型 */</span> <span class="hljs-comment">// 1.构造方法私有化,不允许外部直接创建对象</span> <span class="hljs-keyword">private</span> <span class="hljs-title">Singleton2</span>() { } <span class="hljs-comment">// 2.创建类的唯一实例,使用private static 修饰</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Singleton2 instance; <span class="hljs-comment">// 3.提供一个用于获取实例的方法,使用public static 修饰</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">synchronized</span> Singleton2 <span class="hljs-title">getInstance</span>() { <span class="hljs-keyword">if</span> (instance == <span class="hljs-keyword">null</span>) {<span class="hljs-comment">// 如果实例为空则创建这个实例</span> instance = <span class="hljs-keyword">new</span> Singleton2(); } <span class="hljs-keyword">return</span> instance; } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li></ul>

    在getInstance()方法中添加了synchronized关键字,也就是getInstance()是个同步方法,这就是上面所说的在多线程情况下保证单例对象唯一性的手段。细想一下,大家可能会发现一个问题,即使instance已经被初始化(第一次调用就会被初始化),每次调用getInstance()方法都会进行同步,这样会消耗不必要的资源,这也是懒汉单例模式存在的问题。 总结一下懒汉模式的优点是单例只有在使用时才会被实例化,在一定程度上节约了资源;缺点是第一次加载时需要及时进行实例化,反应稍慢,最大的问题是每次调用getInstance()方法都进行同步,造成不必要的同步开销。因此这种模式一般不建议使用。

    Double Check Lock(DCL)方式

    DCL方式实现单例模式的优点在于既能在需要时才初始化单例,又能够保证线程安全,且单例对象初始化后调用getInstance()不进行同步锁。代码实现如下:

    <code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> Singleton{ <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Singleton instance; <span class="hljs-keyword">private</span> <span class="hljs-title">Singleton</span>(){} <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title">getInstance</span>(){ <span class="hljs-keyword">if</span>(instance==<span class="hljs-keyword">null</span>){ synchronized(Singleton.class){ <span class="hljs-keyword">if</span>(instance==<span class="hljs-keyword">null</span>) instance=<span class="hljs-keyword">new</span> Singleton(); } } <span class="hljs-keyword">return</span> instance; } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li></ul>

    本程序的亮点自然都在getInstance()方法上,可以看到getInstance()对instance进行了两次判空:第一层判断主要是为了避免不必要的同步,第二层判断则是为了在null的情况下创建单例。这让人觉得很奇怪,不是已经判断过了吗?是不是有点摸不着头脑,下面就一起来分析一下。 假设线程A执行到 instance=new Singleton()这条语句时,这里看起来是一句代码,但实际上它并不是一个原子操作,这句代码最终会被编译成多条汇编指令,它大致做了三件事:

    (1). 给Singleton的实例分配内存空间; (2). 调用Singleton的构造函数,初始化成员字段; (3). 将instance对象指向分配的内存空间(此时instance就不为null了)。

    但是由于Java编译器允许处理器乱序执行,以及JDK 1.5之前JMM(Java Memory Model,Java 内存模型)中Cache,寄存器到主内存回写顺序的规定,上面的第二步和第三步的顺序是无法保证的。也就是说,执行顺序可能是1-2-3 也可能是 1-3-2.如果是后者,并且3执行完毕,2未执行之前,被切换到B线程上,这个时候instance因为已经在线程A内执行过了第三点,instance已经是非空了,所以B线程直接取走instance,再使用时就会出错,这就是DCL失效问题,而且这种问题难于跟踪难于重现,很可能会隐藏很久。

    在JDK1.5之后,SUN 官方已经注意到这种问题,调整了JVM,具体化了volatile关键字,因此,如果JDK1.5或是之后的版本,只需要将instance的定义改成

    private volatile static Singleton instance=null;

    就可以保证instance对象每次都是从主内存中读取,就可以使用DCL写法来完成单例模式。当然,volatile或多或少也会影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。

    DCL优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化,效率高。 缺点:第一次加载时反应稍慢,也由于Java内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生概率很小。

    DCL模式是使用最多的单例实现方式,它能够在需要时才实例化单例对象,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于JDK 6 版本下使用,否则,这种方式一般能满足要求。

    静态内部类单例模式

    DCL 虽然在一定程度上解决了资源消耗,多余的同步,线程安全等问题,但是它还是在某些情况下失效。这个问题被称为双重检查锁定(DCL)失效,在《Java Concurrency in Practice》一书中谈到了这个问题,并指出这种”优化”是丑陋的,不赞成使用。而建议使用如下的代码代替:

    <code class="hljs java has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Singleton</span>{</span> <span class="hljs-keyword">private</span> Singleton{} <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title">getInstance</span>(){ <span class="hljs-keyword">return</span> SingletonHolder.sInstance; } <span class="hljs-javadoc">/** * 静态内部类 */</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SingletonHolder</span>{</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> Singleton sInstance=<span class="hljs-keyword">new</span> Singleton(); } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul>

      当第一次加载Singleton类是并不会初始化sInstance,只有在第一次调用Singleton的getInstance()方法时才会导致sInstance被初始化。因此,第一次调用getInstance()方法会导致虚拟机加载SingletonHolder类,这种方法不仅能够保证线程安全,也能够保证单例对象的唯一性,同时也延迟了单例的实例化,所以这是推荐使用的单例模式实现方式。

    枚举单例

    枚举方式是一种更加简单的操作。

    <code class="hljs cs has-numbering"> <span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> SingletonEnum{ INSTANCE; <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doSomething</span>(){ System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"do sth."</span>); } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>

    使用时直接调用SingletonEnum.INSTANCE.doSomething()搞定。简单高效,而且枚举天生不存在并发问题,是线程安全的。至于为什么线程安全的,请参照另外一篇博客Java 枚举深入分析,这里不再给出。

    防止反序列化导致对象重新生成

    说到这里,在构建单例模式的问题上,我们还有一个问题没有解决,就是如何解决反序列化导致的对象重新生成问题。关于序列化的详细内容,请参考另一篇文章Java 序列化深入分析。 简单来说,如果单例类实现序列化接口而不对序列化过程做控制,那么将采用Java默认的序列化机制,从流中恢复后的对象和原来写入的对象不是同一个,也就是说对象重新生成了,这显然是我们不愿意看到的。 下面的例子可以说明: Singleton1.java

    <code class="hljs java has-numbering"><span class="hljs-keyword">package</span> testSingleton; <span class="hljs-keyword">import</span> java.io.ObjectStreamException; <span class="hljs-keyword">import</span> java.io.Serializable; <span class="hljs-javadoc">/** *<span class="hljs-javadoctag"> @author</span> bridge */</span> <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Singleton1</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Serializable</span> {</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Singleton1 instance = <span class="hljs-keyword">null</span>; <span class="hljs-keyword">private</span> <span class="hljs-title">Singleton1</span>() { } <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton1 <span class="hljs-title">getInstance</span>() { <span class="hljs-keyword">if</span> (instance == <span class="hljs-keyword">null</span>) { <span class="hljs-keyword">synchronized</span> (Singleton1.class) { <span class="hljs-keyword">if</span> (instance == <span class="hljs-keyword">null</span>) { instance = <span class="hljs-keyword">new</span> Singleton1(); } } } <span class="hljs-keyword">return</span> instance; } <span class="hljs-comment">// 防止反序列化获取多个对象的漏洞</span> <span class="hljs-keyword">private</span> Object <span class="hljs-title">readResolve</span>() <span class="hljs-keyword">throws</span> ObjectStreamException { <span class="hljs-keyword">return</span> instance; } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li></ul>

    Singleton2.java

    <code class="hljs java has-numbering"><span class="hljs-keyword">package</span> testSingleton; <span class="hljs-keyword">import</span> java.io.Serializable; <span class="hljs-javadoc">/** *<span class="hljs-javadoctag"> @author</span> bridge */</span> <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Singleton2</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Serializable</span>{</span> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Singleton2 instance = <span class="hljs-keyword">null</span>; <span class="hljs-keyword">private</span> <span class="hljs-title">Singleton2</span>() { } <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton2 <span class="hljs-title">getInstance</span>() { <span class="hljs-keyword">if</span> (instance == <span class="hljs-keyword">null</span>) { <span class="hljs-keyword">synchronized</span> (Singleton2.class) { <span class="hljs-keyword">if</span> (instance == <span class="hljs-keyword">null</span>) { instance = <span class="hljs-keyword">new</span> Singleton2(); } } } <span class="hljs-keyword">return</span> instance; } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li></ul>

    测试类 Test.java

    <code class="hljs java has-numbering"><span class="hljs-keyword">package</span> testSingleton; <span class="hljs-keyword">import</span> java.io.*; <span class="hljs-javadoc">/** *<span class="hljs-javadoctag"> @author</span> bridge */</span> <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Test</span> {</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) <span class="hljs-keyword">throws</span> IOException, ClassNotFoundException { Singleton1 s1 = Singleton1.getInstance(); Singleton2 s2 = Singleton2.getInstance(); write(s1, <span class="hljs-string">"s1.dat"</span>); write(s2, <span class="hljs-string">"s2.dat"</span>); Singleton1 s11 = (Singleton1) read(<span class="hljs-string">"s1.dat"</span>); Singleton2 s22 = (Singleton2) read(<span class="hljs-string">"s2.dat"</span>); System.out.println(<span class="hljs-string">"s1定义了readResolve()方法,序列化前"</span> + s1 + <span class="hljs-string">",反序列化后"</span> + s11); System.out.println(<span class="hljs-string">"s2采用默认序列化机制,序列化前"</span> + s2 + <span class="hljs-string">",反序列化后"</span> + s22); } <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">write</span>(Object s, String fileName) <span class="hljs-keyword">throws</span> IOException { FileOutputStream fos = <span class="hljs-keyword">new</span> FileOutputStream(fileName); ObjectOutputStream oos = <span class="hljs-keyword">new</span> ObjectOutputStream(fos); oos.writeObject(s); oos.close(); fos.close(); } <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title">read</span>(String fileName) <span class="hljs-keyword">throws</span> IOException, ClassNotFoundException { ObjectInputStream ois = <span class="hljs-keyword">new</span> ObjectInputStream(<span class="hljs-keyword">new</span> FileInputStream(fileName)); <span class="hljs-comment">// 如果对象定义了readResolve()方法,readObject()会调用readResolve()方法。从而解决反序列化的漏洞</span> <span class="hljs-keyword">return</span> ois.readObject(); } } </code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li></ul><div class="save_code tracking-ad" style="display: none;" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li></ul>

    测试结果:

    <code class="hljs avrasm has-numbering">s1定义了readResolve()方法,序列化前testSingleton<span class="hljs-preprocessor">.Singleton</span>1<span class="hljs-localvars">@66848</span>c,反序列化后testSingleton<span class="hljs-preprocessor">.Singleton</span>1<span class="hljs-localvars">@66848</span>c s2采用默认序列化机制,序列化前testSingleton<span class="hljs-preprocessor">.Singleton</span>2@de6f34,反序列化后testSingleton<span class="hljs-preprocessor">.Singleton</span>2<span class="hljs-localvars">@156</span>ee8e</code><ul class="pre-numbering"><li>1</li><li>2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li></ul>

    从上述实际运行结果来看,没有定义readResolve()方法的单例类在反序列化后对象重新生成,因此要解决这个问题,只需定义readResolve()方法,并返回私有静态对象instance即可。

    <code class="hljs java has-numbering"><span class="hljs-comment">// 防止反序列化获取多个对象的漏洞</span> <span class="hljs-keyword">private</span> Object <span class="hljs-title">readResolve</span>() <span class="hljs-keyword">throws</span> ObjectStreamException { <span class="hljs-keyword">return</span> instance; }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>

    容器实现

    在学习了上述各类单例模式的实现之后,再来看看一种另类的实现,具体代码如下:

    <code class="hljs cs has-numbering"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> SingletonManager{ <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Map<String,Object> objMap=<span class="hljs-keyword">new</span> HashMap<>(); <span class="hljs-keyword">private</span> <span class="hljs-title">SingleManager</span>(){} <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">registerService</span>(String key,Object instance){ <span class="hljs-keyword">if</span>(!objMap.containsKey(key)){ objMap.put(key,instance); } } <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title">getService</span>(String key){ <span class="hljs-keyword">return</span> objMap.<span class="hljs-keyword">get</span>(key); } }</code><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank href="javascript:;" target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div><ul class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li></ul>

    在程序的初始,将多种单例类型注入到一个统一的管理类当中,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。

    总结

    不管以哪种方式实现单例模式,它们的核心原理都是将构造函数私有化,并且通过静态方法获取一个唯一的实例。在这个获取的过程中必须保证线程安全,方式反序列化导致重新生成实例对象等问题。选择哪种实现方式取决于项目本身,如是否是复杂的并发环境,JDK版本是否过低,单例对象的资源消耗等。

    转载请注明原文地址: https://ju.6miu.com/read-1298274.html
    最新回复(0)