惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
N
Netflix TechBlog - Medium
V
Visual Studio Blog
博客园 - Franky
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 三生石上(FineUI控件)
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
宝玉的分享
宝玉的分享
量子位
大猫的无限游戏
大猫的无限游戏
人人都是产品经理
人人都是产品经理
V
V2EX
The Cloudflare Blog
月光博客
月光博客
Last Week in AI
Last Week in AI
雷峰网
雷峰网
WordPress大学
WordPress大学
博客园 - 【当耐特】
博客园 - 聂微东
IT之家
IT之家
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻

博客园 - Birdshover

express-partials与express4.x不兼容问题 选择结构(if..else..,switch,try..catch..)的拆分 该省代码的地方要省,反之亦然。 dell笔记本的Broadcom 802.11b/g 无线网卡ubuntu 9.10下安装 四步完成跨平台调用——thrift的开发应用 facebookde 的 NoSQL数据库cassandra的配置与调用(java&&c#) VirtualBox 虚拟机 Debian系统上安装Cassandra步骤及遇到的问题 大道化简,重构的艺术 读自己的书,走自己的路 厨师炒菜与网站反爬虫问题 jre6的webservice使用Php类库nusoap调用的问题 - Birdshover - 博客园 mysql master/slave 使用感受 java开发守护进程 实现高效易用的java操作mysql包装 部分NLuke版本源码更新(2009-11-1) 一个不必要的设计 网站、数据库的衍变之路(六) 网站、数据库的衍变之路(五) ASP.NET Forms验证的安全性问题研究——为什么加密代码需要配置为服务
单件模式的陷阱
Birdshover · 2009-10-31 · via 博客园 - Birdshover

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

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

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

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