单例模式易错分析

最近在学些设计模式,今天记录的是单例模式,单例模式在平时的工作中运用的还是比较多的,是一种常用的软件设计模式,通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式将是您的是最好的解决方案。

先上代码:

    public class SigletonCounter
    {
        //static SigletonCounter instance = new SigletonCounter();
        static SigletonCounter instance = null;//将类定义为静态类
        public static SigletonCounter Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new SigletonCounter();//实例化该类
                }
                return instance;
            }
        }
        private int totnum = 0;//计数用
        public void add()
        {
            totnum++;
        }
        public int GetCounter()
        {
            return totnum;
        }
  }

以上的代码就是一个简单的单例实例了,在一般的单一线程程序中,看上去是足够了,但是在多线程的情况下似乎就达不到单例的效果了,有可能出现两个线程同时判断了if (instance == null)这个方法,导致了存在两个实例,造成错误,下面我们来验证下这样的情况,上代码:

        /// <summary>
        /// 用于显示
        /// </summary>
        void Write()
        {
            string results = "";
            SigletonCounter mycounter = SigletonCounter.Instance;
            for (int i = 0; i < 3; i++)
            {
                mycounter.add();
                results += Thread.CurrentThread.Name.ToString() + "对应";
                results += "当前的计数:";
                results += mycounter.GetCounter().ToString();
                results += "\n";
                Console .WriteLine ( results);
                results = "";
            }
        }
            {       Thread thread0 = Thread.CurrentThread;
            thread0.Name = "线程 0";
            Thread thread1 = new Thread(new ThreadStart(DOwork));
            thread1.Name = "线程 1";
            Thread thread2 = new Thread(new ThreadStart(DOwork));
            thread2.Name = "线程 2";
            Thread thread3 = new Thread(new ThreadStart(DOwork));
            thread3.Name = "线程 3";
            thread1.Start();
            thread2.Start();
            thread3.Start();
            Write();       }

运行一下结果为:

线程 3对应当前的计数:1

线程 3对应当前的计数:3

线程 3对应当前的计数:4

线程 1对应当前的计数:1

线程 1对应当前的计数:2

线程 1对应当前的计数:3

线程 0对应当前的计数:5

线程 0对应当前的计数:6

线程 0对应当前的计数:7

线程 2对应当前的计数:2

线程 2对应当前的计数:8

线程 2对应当前的计数:9

从上面的结果看,和预测的结果是相符合的,其中出现了重复的数,按照单例模式的理论是不应该出现该问题的,totnum的值应该是唯一的,但是事实摆在面前,我们得去解决它。

现在对程序SigletonCounter类做改进如下:

        static SigletonCounter instance = null;//将类定义为静态类
        private int totnum = 0;//计数用
        static readonly object padlock = new object();
        public static SigletonCounter Instance
        {
            get
            {
                if (instance == null)//多加一次判断是因为,此判断在此可提升性能,少做lock操作
                {
                    lock (padlock)
                    {
                        if (instance == null)
                        {
                            instance = new SigletonCounter();
                        }

                    }
                }
                return instance;
            }

        }
        public void add()
        {
            totnum++;
        }
        public int GetCounter()
        {
            return totnum;
        }

运行得到结果是:

线程 1对应当前的计数:1

线程 1对应当前的计数:2

线程 1对应当前的计数:3

线程 0对应当前的计数:4

线程 0对应当前的计数:5

线程 0对应当前的计数:6

线程 3对应当前的计数:7

线程 3对应当前的计数:8

线程 3对应当前的计数:9

线程 2对应当前的计数:10

线程 2对应当前的计数:11

线程 2对应当前的计数:12

这样的就达到我们的目的了,在同一个时刻加了锁的那部分程序只有一个线程可以进入,避免了同时判断造成的错误,

时间: 2024-10-09 20:44:26

单例模式易错分析的相关文章

C语言指针与数组的定义与声明易错分析

部分摘自<C语言深度解剖> 1.定义为数组,声明为指针 在文件1中定义: char a[100]; 在文件2中声明: extern char *a; //这样是错误的 这里的extern告诉编译器a这个名字已经在别的文件中被定义了,下面的代码使用的a是在别的文件中定义的.编译器是按文件分别编译的,当a被声明为char* a时,编译器理所当然的认为a是一个指针变量,在32位系统下占用4个byte,这4个byte存放的是地址,地址指向的空间存储的是char类型数据. 程序会返回SIGSEGV. 2

js undefined易错分析

undefined 以下是错误写法: data = undefined; alert(undefined==false);//这样判断会输出false; if(data!=undefined || data!='' || data!=null || data!="/"){ alert(111); //*会输出111,因为先判断data!=undefined为假后,再开始继续判断data!='' return false; }else{ alert(222); return true;

集合框架中,引用数据类型对象集合的构建,易错点分析

先来个完整的效果代码:其目的创建5个学生对象,录入姓名与年龄的对象,并用集合将其遍历出来.易错点:1.如果不在学生对象中重写toString方法,那么在集合中就会出现,遍历出来后的arr{i}全部都是引用的对象地址,并不是对象.2.引用数据类型与基本数据类型最大的区别,引用数据类型要事先定义好各项属性与方法.要知道有参构造的作用是初始化方法里的属性.3.定义setname().getname().setage().getage()这四个方法,通过这四个方法来实现对name和age的操作.这样一来

细节!重点!易错点!--面试java基础篇(一)

今天来给大家分享一下java的重点易错点部分,也是各位同学面试需要准备的,欢迎大家交流指正. 1.java中的main方法是静态方法,即方法中的代码是存储在静态存储区的. 2.任何静态代码块都会在main方法之前执行. 3.java程序的初始化顺序:原则:静态优先于非静态,且只初始化一次:父类优先于子类:按照成员定义顺序初始化.例顺序:父类静态变量,父类静态代码块,子类静态变量,子类静态代码块,父类非静态变量,父类非静态代码块,父类构造函数,子类非静态变量,子类非静态代码块,子类构造函数. 4.

细节!重点!易错点!--面试java基础篇(二)

今天来给大家分享一下java的重点易错点第二部分,也是各位同学面试需要准备的,欢迎大家交流指正. 1.字符串创建与存储机制:当创建一个字符串时,首先会在常量池中查找是否已经有相同的字符串被定义,其判断的依据是String类型equals的返回值,若已经定义,则直接获取对其的引用.此时不需要创建新的对象,如果没有定义,首先创建这个对象,然后把它加入到字符串池中,再将它的引用返回.(例:new String(”aaa“)可能创建了1个或者2个对象,如果常量池中原来有aaa那么之创建了一个对象,如果没

指针重难、易错点

(一)函数指针 在前边的blog中,已经整理归纳了数组和初级指针,接下来,我来继续整理高级指针和如何正确使用指针. 我们说过,指针数组是一个数组,每个元素是指针:数组指针是个指针,指向的是数组.所以: 函数指针就是指向函数的指针.我们先看以下代码: <pre name="code" class="cpp">void fun() { } int main() { printf("%p",fun); printf("%p&quo

Javascript易错知识点

? JS易错知识点总结: == 和 === 的区别: ==:判断两个变量的值是否相等. ===:判断两个变量的类型和值是否都相等,两个条件同时满足时,表达式为True. switch中break的作用: 如果一个case后面的语句,没有写break,那么程序会向下执行,而不会退出: 例如:当满足条件的case 2下面没有break时,case 3也会执行 1 var num = 2; 2 switch(num){ 3 case 1: 4 alert('case 1'); 5 break; 6 c

黑马程序员---C基础3【变量的易错】【程序结构】【if语句】【Switch语句】

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- [变量的易错] 1.变量为什么要初始化为0 int  sum,a=3: sum = sum+a 如果未初始化则会成为一个不确定的变量,结果也会不确定,容易出错. 2.不同类型的变量之间的转换 切记int  a=1,b=0:b=1-1.5:其中b为一个整型所有结果是保留整数部分的0,而不是-0.5,又因为0没有正负之分,所有保存结果为b=0: 3.关于Xcode的一个快速注释的插件 快捷键://

JavaScript易错知识点整理

本文是我学习JavaScript过程中收集与整理的一些易错知识点,将分别从变量作用域,类型比较,this指向,函数参数,闭包问题及对象拷贝与赋值这6个方面进行由浅入深的介绍和讲解,其中也涉及了一些ES6的知识点. JavaScript知识点 1.变量作用域 var a = 1; function test() { var a = 2; console.log(a); // 2 } test(); 上方的函数作用域中声明并赋值了a,且在console之上,所以遵循就近原则输出a等于2. var a