最近被多线程问题(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. python实现无重复字符串的最长子串

    给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度. 示例 1: 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc&qu ...

  2. #424 Div2 C

    #424 Div2 C 题意 给出 k 个分数,初始分数未知,按顺序把这 k 个分数加到初始分数上,已知 n 个加入分数后的结果(无序),问初始分数有多少种可能. 分析 也就是说这 n 个结果,它们之 ...

  3. 编码/解码和进制转化工具hURL

    编码/解码和进制转化工具hURL   在安全应用中,各种编码方式被广泛应用,如URL编码.HTML编码.BASE64等.而在数据分析时候,各种进制的转化也尤为频繁.为了方便解决这类问题,Kali Li ...

  4. [BZOJ 1800] 飞行棋

    Link: BZOJ 1800 传送门 Solution: $O(n^4)$…… Code: #include <bits/stdc++.h> using namespace std; ] ...

  5. [BZOJ 1806] Miners 矿工配餐

    Link: BZOJ 1806 传送门 Solution: 为了使状态包含每个节点前所有必须的信息: 设$dp[i][a1][a2][b1][b2]$为配送到第$i$个,一厂前两个为$a1,a2$,二 ...

  6. 用Xcode6的Leaks检测内存泄漏

    用xcode打开项目之后,选择Product - Profile: 在弹出的窗口中选择Leaks: 然后在设备解锁的情况下,选择Leaks再点击左上角的红色按钮开始运行APP: 红色的柱子表示有内存泄 ...

  7. DATASNAP数据序列之FIREDAC的TFDJSONDataSets

    DATASNAP数据序列之FIREDAC的TFDJSONDataSets DELPHI XE5开始增加了新的数据引擎——FIREDAC,它是跨平台的数据引擎,WINDOWS.LINUX.MAC.APP ...

  8. 咏南3层数据集控件--TYNDataSet

    咏南3层数据集控件--TYNDataSet 和2层CS数据集的语法非常近似.有了这个控件,学习掌握3层开发变得如此地简单. 新增数据: procedure Tfunit.btnappendClick( ...

  9. iOS -- tableView顶部留白

    最近iOS 11 下来之后需要适配系统,故此测试系统发现同一种机器上tableView与顶部控件,或者导航栏之间的有大量的留白: 这个问题是有的界面有,有的界面无: 修改与导航栏或者其他控件之间的距离 ...

  10. centos7 安装LNMP(php7)之mysql安装,更改密码,远程授权

    1.执行命令 yum install mysql mysql-server mysql-devel -y 知道出现complete!则安装mysql完成 当执行 service mysqld rest ...