多线程问题与double-check

在一个多线程程序中,如果共享资源同时被多个线程使用,就有可能会造成多线程问题,这主要取决于针对该资源的某项操作是否是线程安全的。例如,.Net中的dictionary就是一个完全线程不安全的数据结构,对于dictionary的插入、删除都有可能带来多线程问题,这主要是由于dictionary的内部实现结构会频繁的由于插入、删除操作而改变长度,这时,如果出现多线程问题,程序最可能抛出数组越界的Exception。特别的,对于WebService来讲,每一个请求都会生成一个thread/instance,因此就要特别注意多线程问题了。

一般地,多线程问题常常发生于对于共享资源的同时使用。例如,对于类的成员变量的使用,对于全局静态变量的使用,而对于函数内部的局部变量而言,一般式不会存在多线程问题的,因为每个线程在调用一个特定的函数时,都会生成一份函数内部成员变量的副本,线程和线程之间是互不相干的。
解决多线程问题,最常见的方式就是加锁,使得某一资源在同一时刻只能被一个线程所用,而其他线程则必须在被加锁的代码外等待,直到锁被解除,例如如下c#代码所示:

1             lock (_lock)
2             {
3                 //do something to the shared resources.
4             }

下面说说double-check。
多线程问题也常常和一种lazy-initialize的设计模式联系在一起。在这里就会慢慢引出double-check。lazy-initialize讲的是,对于一些特别复杂的对象,让程序在第一次调用它的时候再对它进行初始化,而且保证仅仅初始化一次。
首先想到的设计是这样的:

 1     class A
 2     {
 3
 4
 5         private ComplexClass _result = null;
 6
 7         public ComplexClass GetResult()
 8         {
 9             if (_result == null)
10             {
11                 _result = new ComplexClass();
12             }
13             return _result;
14         }
15     }

但是这样有一个问题。ComplexClass的构造过程较长的话,当第一个线程还在进行ComplexClass构造的时候,_result可能是null,也可能指向了一个尚未初始化完成的对象。这样,要么两个线程初始化了两次ComplexClass,要么第二个线程会返回一个指向不完整对象的引用。所以,在这里需要用到一个锁,如下所示:

 1         ComplexClass GetResult()
 2         {
 3             lock (_lock)
 4             {
 5                 if (_result == null)
 6                 {
 7                     _result = new ComplexClass();
 8                 }
 9             }
10             return _result;
11         }

这样,虽然多线程的问题解决了,但是每一次需要使用result时都会请求锁,而请求锁对程序的性能是有很大影响的,因此我们在lock的外面再加一层check:

 1         ComplexClass GetResult()
 2         {
 3             if (_result == null)
 4             {
 5                 lock (_lock)
 6                 {
 7                     if (_result == null)
 8                     {
 9                         _result = new ComplexClass();
10                     }
11                 }
12             }
13             return _result;
14         }

这样,对于所有初始化完成后的请求,就都不用请求锁,而是直接返回_result。
但是还是存在一点问题。对于一些编程语言来说,_result = new ComplexClass();这句代码会使得_result指向一个部分初始化的对象。也就是说,当线程A在初始化ComplexClass时,线程B有可能会判断_result已经不是null了,而这时其实初始化尚未完成,这时线程B就直接返回了一个部分初始化的对象,会造成程序的崩溃。那么,这个问题怎么解决呢?一般的解决方法是在程序内部再加一个局部变量(标识变量)做一层缓冲:

 1         ComplexClass GetResult()
 2         {
 3             ComplexClass result;
 4             if (_result == null)
 5             {
 6                 lock (_lock)
 7                 {
 8                     if (_result == null)
 9                     {
10                         result = new ComplexClass();
11                         _result = result;
12                     }
13                 }
14             }
15             return _result;
16         }

这样,上面的问题就彻底解决了~

时间: 2024-10-10 23:27:55

多线程问题与double-check的相关文章

Double Check形式的单例模式

这两天在看开源项目时,发现Event Bus和Universalimageloader中写单例模式都是Double Check的形式.平时总是看到各种各样的单例模式,如,饿汉式,懒汉式等等.其中大多存在问题.今天记录一种比较优秀的单例模式的写法------Double Check.以后我准备就用这种方法了.(虽然还有其他优秀的方式,比如内部静态类,枚举) 先上代码: public class ImageLoader { private volatile static ImageLoader in

多线程下的单例-double check

话不多说直接上代码: public sealed class Singleton { private static Singleton _instance = null; // Creates an syn object. private static readonly object SynObject = new object(); Singleton() { } public static Singleton Instance { get { // Double-Checked Lockin

【C++设计模式】单件类与DCLP(Double Check Lock Pattern)的风险

[单件类] 保证只能有一个实例化对象,并提供全局的访问入口. [设计注意事项] 1.阻止所有实例化的方法: private 修饰构造函数,赋值构造函数,赋值拷贝函数. 2.定义单实例化对象的方法: a.使用static 修饰 b.使用new+delete的方法 3.多线程版本: 使用双检测锁定,即先检测单实例对象是否存在:不存在,使能"锁",再次判断实例是否存在,不存在就创建该单实例对象. A.单层锁示例: Singleton* Singleton::getInstance() { L

单例模式的两种实现方式对比:DCL (double check idiom)双重检查 和 lazy initialization holder class(静态内部类)

首先这两种方式都是延迟初始化机制,就是当要用到的时候再去初始化. 但是Effective Java书中说过:除非绝对必要,否则就不要这么做. 1. DCL (double checked locking)双重检查: 如果出于性能的考虑而需要对实例域(注意这个属性并没有被static修饰)使用延迟初始化,就使用双重检查模式 public class Singleton { private volatile Singleton uniqueInstance; private Singleton(){

多线程场景下延迟初始化的策略

1.什么是延迟初始化 延迟初始化(lazy initialization,即懒加载)是延迟到需要域的值时才将它初始化的行为.如果永远不需要这个值,这个域就永远不会被初始化.这种方法既静态域,也适用于实例域. 最好建议“除非绝对必要,否则就不要这么做”. 2.延迟初始化线程安全的一个策略:同步 延迟初始化的一个好处,是当域只在类的实例部分被访问,并且初始化这个域的开销很高,那就可能值得进行延迟初始化. 但是在大多数情况下,正常的初始化要优先于延迟初始化.因为在多线程的场景下,采用某种形式的同步是很

单例和多线程

要保证在多线程环境下的单例模式,有下面两种建议的方式: 一.静态内部类 public class Singletion { private static class InnerSingletion { private static Singletion single = new Singletion(); } public static Singletion getInstance(){ return InnerSingletion.single; } }  二.double check的方式

Double Checked Locking 模式

转自:http://blog.csdn.net/wwsoon/article/details/1485886 之前在使用Double Check Locking 模式时,发现自己还是不太理解.于是写个记录,其实很简单,一看就明白了.应用特别说明:1.Double Check Locking模式是singleton的多线程版本,如果是单线程则应使用singleton.2.Double Check Locking模式依就会使用锁--临界区锁定,不要以为可以避免使用锁.3.Double Check L

线程学习--(六)单例和多线程、ThreadLocal

一.ThreadLocal 使用wait/notify方式实现的线程安全,性能将受到很大影响.解决方案是用空间换时间,不用锁也能实现线程安全. 来看一个小例子,在线程内的set.get就是threadLocal package thread2; public class ConnThreadLocal { public static ThreadLocal<String> th = new ThreadLocal<String>(); public void setTh(Strin

架构师养成记--6.单例和多线程、ThreadLocal

一.ThreadLocal 使用wait/notify方式实现的线程安全,性能将受到很大影响.解决方案是用空间换时间,不用锁也能实现线程安全. 来看一个小例子,在线程内的set.get就是threadLocal 1 public class ConnThreadLocal { 2 3 public static ThreadLocal<String> th = new ThreadLocal<String>(); 4 5 public void setTh(String value

[转] Java多线程发展简史

这篇文章,大部分内容,是周五我做的一个关于如何进行Java多线程编程的Knowledge Sharing的一个整理,我希望能对Java从第一个版本开始,在多线程编程方面的大事件和发展脉络有一个描述,并且提及一些在多线程编程方面常见的问 题.对于Java程序员来说,如果从历史的角度去了解一门语言一个特性的演进,或许能有不同收获. 引言 首先问这样一个问题,如果提到Java多线程编程,你会想到什么? ● volatile.synchronized关键字? ● 竞争和同步? ● 锁机制? ● 线程安全