单件模式的陷阱

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

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

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

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的文章讲到了一个完成示例,但是没有描述可能会遇到这个问题。

posted @ 2009-10-31 21:35 Birdshover 阅读(...) 评论(...) 编辑 收藏