艾伟_转载:单件模式的陷阱

  看过很多单件模式的文章,书上有,网上更多一些。一般来说,只有如何实现单件模式,而没有介绍具体情况单件模式的使用,也没有介绍过单件模式会出现问题。单件模式似乎不会产生逻辑上的问题。但是,这仅仅是似乎。

  在描述我遇到的问题之前,先讲讲我对其原理的理解。

  首先单件模式是自我创建的一个对象,并且在运行期始终保持只有唯一的对象。抛开什么东西能够自我创建不说,保持唯一对象要怎么理解呢?先看看一个普通的类:

package singleton;

public class SimpleClass {

}

  对其进行单元测试:

SimpleClass s3 = new SimpleClass();
SimpleClass s4 = new SimpleClass();
Assert.assertNotSame(s3, s4);

  该测试总是正确的,s3和s4虽然是同一个类型,但是不是同一个东西。SimpleClass可以是苹果,是一种东西,而s3、s4是具体的一个苹果。世界上没有哪两片树叶是完全一样的,但是他们都是树叶。

  就.net、java这种单根继承,具有垃圾回收机制的平台而言,s3和s4是被放到heap的两个地方的。可以用图1来描述。s3和s4都有其独立性。

图1

  而单件模式产生的结果是什么呢?单件模式就是相当于这里的s3和s4都指向了001144这个地址。其结果是他们是同一个类型,同时也是同一个东西。可以理解为,大熊猫快要灭绝了(要保护大熊猫,嘿嘿,贫道喜欢大熊猫),世界上就一个大熊猫了。于是,大家都去看望它。而每个人眼里的大熊猫都是一个,拍出来的照片都是记录的同一个大熊猫。

  单件模式基本代码为:

package singleton;

/**
 * @author Birdshover
 * @url http://birdshover.cnblogs.com
 */
public class SingletonClass {
    private SingletonClass(){
    }
    
    private static SingletonClass instance;
    /**
     * @return
     */
    public static SingletonClass getInstance(){
        if(instance == null){
            synchronized(SingletonClass.class){
                if(instance == null){
                    instance = new SingletonClass();
                }
            }
        }
        return instance;
    }
}

  对其进行单元测试:

SingletonClass s1 = SingletonClass.getInstance();
SingletonClass s2 = SingletonClass.getInstance();
        
Assert.assertSame(s1, s2);

  结果符合预期。

  现在的问题是,无论是谁拿到了SingletonClass的实例都是同一个东西,那么SingletonClass相当于是被静态化了。

  假如我现在有个类A,而A有静态字段B。

class A{

       static B b = new B();

class B{

     private int f;

     public void W(){ f++; }
}

  我就假定,其它地方没有对b有赋值的操作,那么b在系统中也是一个单件,当然它不是单件模式。而A对象的数目是不定的,因此,A的N多实例对b的方法W的范围虽然安全,但是如果b里面含有字段f,而W对f有赋值操作,是不是有问题了?

  也就是说,B如果有状态,那么就会造成麻烦。试想一下,现在有张画是黑的,有2位画家正准备改变其颜色。如果2位画家排队来操作,那么,但第一位画家操作完后,他可以告诉大家,现在画是蓝的。而第二位画家修改完后可以说画是红的。那如果两位画家一起画,第一位画完了,而第二位正在画。第一位画家宣布画是蓝的的时候,大家看到画是蓝色和红色的,两种颜色都有。这就是Singleton模式的陷阱。为了表现这个陷阱,修改了SingletonClass代码:

package singleton;

/**
 * @author Birdshover
 * @url http://birdshover.cnblogs.com
 */
public class SingletonClass {
    private SingletonClass(){
    }
    
    private static SingletonClass instance;
    /**
     * @return
     */
    public static SingletonClass getInstance(){
        if(instance == null){
            synchronized(SingletonClass.class){
                if(instance == null){
                    instance = new SingletonClass();
                }
            }
        }
        return instance;
    }
    
    private int value;
    
    public void Raise(){
        for(int i = 0;i < 10;i++){
            value++;
            System.out.println("Thread:" + Thread.currentThread().getId());
        }
    }
    
    /**
     * @return
     */
    public int getValue(){
        return value;
    }

    /**
     * @param value the value to set
     */
    public void setValue(int value) {
        this.value = value;
    }
}

  再准备好多线程测试的单元测试代码:

package unittest.singleton;

import java.util.Random;

import singleton.SimpleClass;
import singleton.SingletonClass;
import junit.framework.Assert;
import junit.framework.TestCase;

public class SingletonClassTest extends TestCase  {
    static final Random r =new Random();

    public void testThread() throws InterruptedException{
        for(int i = 0;i < 100;i++){
            Thread thread = new Thread(new MyThread());
            thread.start();
        }
        Thread.currentThread().sleep(2000);
        SingletonClass s1 = SingletonClass.getInstance();
        System.out.println(s1.getValue());
    }
    
    private class MyThread implements Runnable
    {
        public void run() {
            SingletonClass s1 = SingletonClass.getInstance();
            s1.Raise();
            System.out.println("value:" + s1.getValue());
        }
    }
}

  测试完成后发生了什么?我摘录上一组数据的几部分片段:

  Thread:8

  Thread:8

  Thread:8

  Thread:8

  Thread:8

  Thread:8

  Thread:8

  Thread:8

  Thread:8

  Thread:8

  value:10

  这部分结果表明,有时候只有一个线程在运行(我的是单核CPU),值是正确的。

  Thread:12

  Thread:13

  Thread:14

  Thread:15

  Thread:16

  Thread:17

  Thread:18

  Thread:19

  Thread:20

  Thread:22

  Thread:21

  Thread:23

  Thread:24

  这部分结果表明,线程执行顺序开始混乱了。

  value:1000

  value:1000

  value:1000

  value:1000

  value:1000

  这部分结果表明,后面几个线程拿到的数据是一样的,有可能会造成预期上的偏差,从而产生逻辑上的错误。

  话说到这里就讲出了单件模式的陷阱。我又去网上随便翻阅了一下,大多数文章都是点在了如果构建单件模式,没有讲网站的实例。而TerryLee的文章讲到了一个完成示例,但是没有描述可能会遇到这个问题。

时间: 2024-10-23 15:17:49

艾伟_转载:单件模式的陷阱的相关文章

艾伟_转载:WPF/Silverlight陷阱:XAML自定义控件的嵌套内容无法通过名称访问

为了说明这个问题,假定我们需要实现一个具有特殊功能的按钮控件.编写Xaml文件如下: <Button x:Class="TestWpf.XamlButton"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">Button> 对

单件模式的陷阱

看过很多单件模式的文章,书上有,网上更多一些.一般来说,只有如何实现单件模式,而没有介绍具体情况单件模式的使用,也没有介绍过单件模式会出现问题.单件模式似乎不会产生逻辑上的问题.但是,这仅仅是似乎. 在描述我遇到的问题之前,先讲讲我对其原理的理解. 首先单件模式是自我创建的一个对象,并且在运行期始终保持只有唯一的对象.抛开什么东西能够自我创建不说,保持唯一对象要怎么理解呢?先看看一个普通的类: package singleton; public class SimpleClass { } 对其进

艾伟_转载:.NET设计模式:单件模式(Singleton Pattern)

概述 Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点.这就提出了一个问题:如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?客户程序在调用某一个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任,而不是类使用者的责任. 从另一个角度来说,Singleton模式其实也是一种职责型模式.因为我们创建了一个对象,这个对象扮演了独一无二的角色,在这个单独的对象实例中,它集中了它所属类的所有权力,同时它也肩负了行使这种权力的职责! 意图

艾伟_转载:.NET设计模式:抽象工厂模式(Abstract Factory)

概述 在软件系统中,经常面临着"一系列相互依赖的对象"的创建工作:同时由于需求的变化,往往存在着更多系列对象的创建工作.如何应对这种变化?如何绕过常规的对象的创建方法(new),提供一种"封装机制"来避免客户程序和这种"多系列具体对象创建工作"的紧耦合?这就是我们要说的抽象工厂模式. 意图 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类. 模型图 逻辑模型: 物理模型: 生活中的例子 抽象工厂的目的是要提供一个创建一系列相关或

艾伟_转载:揭示同步块索引(上):从lock开始

大家都知道引用类型对象除实例字段的开销外,还有两个字段的开销:类型指针和同步块索引(SyncBlockIndex).同步块索引这个东西比起它的兄弟类型指针更少受人关注,显得有点冷落,其实此兄功力非凡,在CLR里可谓叱咤风云,很多功能都要借助它来实现. 接下来我会用三篇来介绍同步块索引在.NET中的所作所为. 既然本章副标题是从lock开始,那我就举几个lock的示例: 代码1 1: public class Singleton 2: { 3: private static object lock

艾伟_转载:Regex.Replace 方法的性能!

    园子里有很多关于去除Html标签的文章.一个常用的经验是使用 Regex.Replace 方法利用正则去替换.这里有一篇使用该方法的文章 C#中如何去除HTML标记 .下面我贴出该方法的代码,见代码清单1-1 代码清单1-1 引用 http://www.cnblogs.com/zoupeiyang/archive/2009/06/22/1508039.html                ///          /// 去除HTML标记         ///          //

艾伟_转载:WCF基本异常处理模式[中篇]

通过WCF基本的异常处理模式[上篇], 我们知道了:在默认的情况下,服务端在执行某个服务操作时抛出的异常(在这里指非FaultException异常),其相关的错误信息仅仅限于服务端可见,并不会被WCF传递到客户端:如果将开启了IncludeExceptionDetailInFaults的ServiceDebug服务行为通过声明(通过在服务类型上应用ServiceBehaviorAttrite特性)或者配置的方式应用到相应的服务上,异常相关的所有细节信息将会原封不动地向客户端传送. 这两种方式体

艾伟_转载:.NET设计模式:工厂方法模式(Factory Method)

概述 在软件系统中,经常面临着"某个对象"的创建工作,由于需求的变化,这个对象的具体实现经常面临着剧烈的变化,但是它却拥有比较稳定的接口.如何应对这种变化?提供一种封装机制来隔离出"这个易变对象"的变化,从而保持系统中"其它依赖该对象的对象"不随着需求的改变而改变?这就是要说的Factory Method模式了. 意图 定义一个用户创建对象的接口,让子类决定实例化哪一个类.Factory Method使一个类的实例化延迟到其子类. 结构图 生活中

艾伟_转载:.NET设计模式:原型模式(Prototype Pattern)

概述 在软件系统中,有时候面临的产品类是动态变化的,而且这个产品类具有一定的等级结构.这时如果用工厂模式,则与产品类等级结构平行的工厂方法类也要随着这种变化而变化,显然不大合适.那么如何封装这种动态的变化?从而使依赖于这些易变对象的客户程序不随着产品类变化? 意图 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 结构图 Prototype模式结构图 生活中的例子 Prototype模式使用原型实例指定创建对象的种类.新产品的原型通常是先于全部产品建立的,这样的原型是被动的,并不