最近被多线程问题(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. 51nod 1873 初中的算术【Java BigDecimal/高精度小数】

    1873 初中的算术 基准时间限制:1 秒 空间限制:131072 KB 分值: 10 难度:2级算法题  收藏  关注 Noder现在上初三了,正在开始复习中考.他每天要计算型如 (a× a× a× ...

  2. HDU 3237 Tree(树链剖分)(线段树区间取反,最大值)

    Tree Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 9123   Accepted: 2411 Description ...

  3. System.getProperty("os.name")

    Here is a handy Java class that useSystem.getProperty("os.name") to detect which type of o ...

  4. MySQL索引,MySQL中索引的限制?

    MySQL中索引的限制: 1.MyISAM存储引擎引键的长度综合不能超过1000字节: 2.BLOB和TEXT类型的列只能创建前缀索引: 3.MySQL目前不支持函数索引: 4.使用!= 或者< ...

  5. Delphi ParamStr 使用方法

    ParamStr(0) 代表程序的路径, ParamStr(1) 代表程序的设置的参数

  6. init_machine 在Kernel中被调用的过程

    以tiny4412为例: arch/arm/mach-exynos/mach-tiny4412.c MACHINE_START(TINY4412, "TINY4412") /* M ...

  7. Android - toolbar navigation 样式

    1.修改title 边距 修改边距使用系统的app属性来引入使用,即: xmlns:app="http://schemas.android.com/apk/res-auto" 1 ...

  8. spring-data-jpa动态条件查询

    //获取动态条件的集合List<Long> list = new ArrayList<Long>(); Long sysUserId = currentUser.getSysU ...

  9. DotnetBrowser高级教程-(4)使用MVC框架4-过滤器

    dotnetbrowser内置了过滤器,所谓过滤器,就是实现了Action前后拦截,请看下例: 1.增加目录Filters,在该目录下增加新的过滤器PerformanceFilter,代码如下: pu ...

  10. 2017.7.1 mysql安装与启动(已验证可以使用)

    下载地址:http://learning.happymmall.com/ 之前一直用解压版安装,启动mysql服务的时候总是失败,这次用mysql installer安装一遍,终于成功启动. 1.下载 ...