最近被多线程问题(multi-thread issue)弄昏了头。以前虽然也知道系统里要考虑多线程问题,也无数次见到double-check的代码,但是由于自己碰到这方面的问题基本上就是从其他地方拷贝一份现成的代码,改吧改吧,也一直没有遇到多线程带来的bug,所以就没有留心。知道年前,一份两三个月前写的代码出现了由于多线程带来的bug,最近写的代码在code review中又被师兄批评没有考虑多线程问题,这才专门去补了补这方面的知识,现在就小结一下multi-thread问题以及在其中经常会用到的double-check。

    在一个多线程程序中,如果共享资源同时被多个线程使用,就有可能会造成多线程问题,这主要取决于针对该资源的某项操作是否是线程安全的。例如,.Net中的dictionary就是一个完全线程不安全的数据结构,对于dictionary的插入、删除都有可能带来多线程问题,这主要是由于dictionary的内部实现结构会频繁的由于插入、删除操作而改变长度,这时,如果出现多线程问题,程序最可能抛出数组越界的Exception。特别的,对于WebService来讲,每一个请求都会生成一个thread/instance,因此就要特别注意多线程问题了。
    一般地,多线程问题常常发生于对于共享资源的同时使用。例如,对于类的成员变量的使用,对于全局静态变量的使用,而对于函数内部的局部变量而言,一般式不会存在多线程问题的,因为每个线程在调用一个特定的函数时,都会生成一份函数内部成员变量的副本,线程和线程之间是互不相干的。
    解决多线程问题,最常见的方式就是加锁,使得某一资源在同一时刻只能被一个线程所用,而其他线程则必须在被加锁的代码外等待,直到锁被解除,例如如下c#代码所示:
lock(_lock) 
{
    //do something to the shared resources.
}
    下面说说double-check。
    多线程问题也常常和一种lazy-initialize的设计模式联系在一起。在这里就会慢慢引出double-check。lazy-initialize讲的是,对于一些特别复杂的对象,让程序在第一次调用它的时候再对它进行初始化,而且保证仅仅初始化一次。
    首先想到的设计是这样的:
Class A
{
    private ComplexClass _result = null;
    public ComplexClass GetResult()
    {
         if(_result == null)
         {
              _result = new ComplexClass();
         }
         return _result;
    }
}
    但是这样有一个问题。ComplexClass的构造过程较长的话,当第一个线程还在进行ComplexClass构造的时候,_result可能是null,也可能指向了一个尚未初始化完成的对象。这样,要么两个线程初始化了两次ComplexClass,要么第二个线程会返回一个指向不完整对象的引用。所以,在这里需要用到一个锁,如下所示:
    ComplexClass GetResult()
    {
        lock(_lock)
        {
             if(_result == null)
             {
                  _result = new ComplexClass();
             }
         }         
         return _result;
    }
    这样,虽然多线程的问题解决了,但是每一次需要使用result时都会请求锁,而请求锁对程序的性能是有很大影响的,因此我们在lock的外面再加一层check:
    ComplexClass GetResult()
    {
        if(_result == null)
        {
            lock(_lock)
            {
                 if(_result == null)
                 {
                      _result = new ComplexClass();
                 }
             }  
         }       
         return _result;
    }
    这样,对于所有初始化完成后的请求,就都不用请求锁,而是直接返回_result。
    但是还是存在一点问题。对于一些编程语言来说,_result = new ComplexClass();这句代码会使得_result指向一个部分初始化的对象。也就是说,当线程A在初始化ComplexClass时,线程B有可能会判断_result已经不是null了,而这时其实初始化尚未完成,这时线程B就直接返回了一个部分初始化的对象,会造成程序的崩溃。那么,这个问题怎么解决呢?一般的解决方法是在程序内部再加一个局部变量(标识变量)做一层缓冲:
    ComplexClass GetResult()
    {
        ComplexClass result;
        if(_result == null)
        {
            lock(_lock)
            {
                 if(_result == null)
                 {
                      result = new ComplexClass();
                      _result = result;
                 }
             }  
         }       
         return _result;
    }
这样,上面的问题就彻底解决了~
 
文章来源 http://blog.sina.com.cn/s/blog_597a437101011o66.html

double check 解决单例模式的多线程并发问题的更多相关文章

  1. 用读写锁三句代码解决多线程并发写入文件 z

    C#使用读写锁三句代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题 在开发程序的过程中,难免少不了写入错误日志这个关键功能.实现这个功能,可以选择使用第三 ...

  2. C# 防止同时调用=========使用读写锁三行代码简单解决多线程并发的问题

    http://www.jb51.net/article/99718.htm     本文主要介绍了C#使用读写锁三行代码简单解决多线程并发写入文件时提示"文件正在由另一进程使用,因此该进程无 ...

  3. java中的双重锁定检查(Double Check Lock)

    原文:http://www.infoq.com/cn/articles/double-checked-locking-with-delay-initialization#theCommentsSect ...

  4. Java多线程核心技术(五)单例模式与多线程

    本文只需要考虑一件事:如何使单例模式遇到多线程是安全的.正确的 1.立即加载 / "饿汉模式" 什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接 ...

  5. DCL,即Double Check Lock,中卫双重检查锁定。

    DCL,即Double Check Lock,中卫双重检查锁定. [Java并发编程]之十六:深入Java内存模型——happen-before规则及其对DCL的分析(含代码) 关于单例.关于DCL: ...

  6. Java 多线程并发编程一览笔录

    Java 多线程并发编程一览笔录 知识体系图: 1.线程是什么? 线程是进程中独立运行的子任务. 2.创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run ...

  7. ASP.NET MVC Filters 4种默认过滤器的使用【附示例】 数据库常见死锁原因及处理 .NET源码中的链表 多线程下C#如何保证线程安全? .net实现支付宝在线支付 彻头彻尾理解单例模式与多线程 App.Config详解及读写操作 判断客户端是iOS还是Android,判断是不是在微信浏览器打开

    ASP.NET MVC Filters 4种默认过滤器的使用[附示例]   过滤器(Filters)的出现使得我们可以在ASP.NET MVC程序里更好的控制浏览器请求过来的URL,不是每个请求都会响 ...

  8. 【Java_多线程并发编程】JUC原子类——原子类中的volatile变量和CAS函数

    JUC中的原子类是依靠volatile变量和Unsafe类中的CAS函数实现的. 1. volatile变量的特性 内存可见性(当一个线程修改volatile变量的值后,另一个线程就可以实时看到此变量 ...

  9. Java 多线程 | 并发知识问答总结

    写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...

随机推荐

  1. 替换Ubuntu默认的登录背景

    Ubuntu默认的登录背景看起来还是比较高贵的,但是作为一个爱折腾的人,当然要换成自己喜欢的背景图了.一开始跟着百度走,进了不少坑,最后还是自己走出来的.先上一个成品照 百度得到的答案几乎都是安装ub ...

  2. JAVA基础加强(张孝祥)_类加载器、分析代理类的作用与原理及AOP概念、分析JVM动态生成的类、实现类似Spring的可配置的AOP框架

    1.类加载器 ·简要介绍什么是类加载器,和类加载器的作用 ·Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader ...

  3. Binary Tree Iterative Traversal

    Preorder class Solution { public: vector<int> preorderTraversal(TreeNode* root) { vector<in ...

  4. MySQL命令show full processlist

    processlist命令的输出结果显示了有哪些线程在运行,可以检查当前数据库的运行状态,两种方式使用这个命令. 1 进入MySQL/bin目录下输入mysqladmin processlist; 2 ...

  5. Ubuntu 16.04安装Shell管理工具PAC Manager

    下载: (链接: https://pan.baidu.com/s/1nvqrVgH 密码: 45wz) 安装: sudo dpkg -i pac-4.5.5.7-all.deb

  6. Winform打砖块游戏制作step by step第6节---画墙(砖块集合)以及双缓冲实现

    一 引子 为了让更多的编程初学者,轻松愉快地掌握面向对象的思考方法,对象继承和多态的妙用,故推出此系列随笔,还望大家多多支持. 预备知识,无GDI画图基础的童鞋请先阅读一篇文章让你彻底弄懂WinFor ...

  7. 全面理解java自动装箱和拆箱(转)

    自动装箱和拆箱从Java 1.5开始引入,目的是将原始类型值转自动地转换成对应的对象.自动装箱与拆箱的机制可以让我们在Java的变量赋值或者是方法调用等情况下使用原始类型或者对象类型更加简单直接. 如 ...

  8. Android如何缓存你的BITMAP对象

    在app中通常最占内存.占流量的元素就是图片了,图片往往又无处不在,特别是伴随着list,GridView或者ViewPager出现,这些图片随着你的滑动操作,时而出现在你的屏幕中,时而消失在屏幕之外 ...

  9. ADOX创建ACCESS数据库列名的数据类型

    Type   属性             指示   Parameter.Field   或   Property   对象的操作类型或数据类型. 设置和返回值 设置或返回下列   DataTypeE ...

  10. JAVA之ArrayList集合

    /** * ArrayList集合讲解 * 集合的添加.遍历.删除 */package com.test; import java.util.*; public class test7 { /**   ...