Hello World
Spiga

您能看出这个Double Check里的问题吗?

2009-09-02 15:11 by 老赵, 8547 visits

昨天在做code review时看到一位同事写了这样的代码。这段代码的目的使用Double Check的做法来保证线程安全的延迟加载。但是我看到这代码之后发现了一个问题,这个问题不是第一次出现。因此,我打算在博客上记录一笔,希望可以给更多人提个醒吧。

假设,我们有这样一个Category类型,记录的是一个树型的分类结构:

public class Category
{
    public int CategoryID { get; set; }

    public List<Category> Children { get; set; }
}

然后,我们需要一个CategoryLoader,提供一个Get方法从ID获得指定的Category对象:

public class CategoryLoader
{
    private object m_mutex = new object();
    private Dictionary<int, Category> m_categories;

    public Category GetCategory(int id)
    {
        if (this.m_categories == null)
        {
            lock (this.m_mutex)
            {
                if (this.m_categories == null)
                {
                    LoadCategories();
                }
            }
        }

        return this.m_categories[id];
    }

    private void LoadCategories()
    {
        this.m_categories = new Dictionary<int, Category>();
        this.Fill(GetCategoryRoots());
    }

    private void Fill(IEnumerable<Category> categories)
    {
        foreach (var cat in categories)
        {
            this.m_categories.Add(cat.CategoryID, cat);
            Fill(cat.Children);
        }
    }

    private IEnumerable<Category> GetCategoryRoots() { ... }
}

代码的逻辑非常简单:使用一个字典作为容器,在GetCategory方法内部使用Double Check的方式来保证线程安全(即多个线程同时访问同一个对象的GetCategory方法不会出现问题)。如果没有加载,在使用LoadCategories方法构造并填充字典。在LoadCategories方法中会获取所有的“根分类”,并调用Fill方法填充字典。Fill方法会将传入的categories集合添加到字典中,并且递归地将它们的子分类也填充至字典中。

只可惜,上面的代码有一些问题,导致Double Check没有能够实现我们预期的效果。您能看出这个问题来吗?

当然,为了演示代码的简单,我省略了很多细节。例如Category的ID缺失或有重复,Category对象不是immutable,Children属性可能会包含null,这可能都会形成问题。不过,我们就暂时不在这方面考究了吧。

晚上我会公布结果(此处)。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

98 条回复

  1. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-02 15:15:00

    我记得什么时候看过类似的帖子

    clr优话会把其中一层取消

    所以当时说解决办法是

    static readonly private Dictionary<int, Category> m_categories =new Dictionary<int, Category> ();

    反正也是延迟创建;

  2. 老赵
    admin
    链接

    老赵 2009-09-02 15:20:00

    @韦恩卑鄙
    CLR不会这样优化的。
    如果是这个原因,所有的double check都应该失效了。
    不过其实double check只要正确就可以了,这里是写错了。

  3. Todd Wei
    *.*.*.*
    链接

    Todd Wei 2009-09-02 15:23:00

    奇怪,怎么不是static的?难道是这个问题?

  4. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-02 15:24:00

    我在当时帖子里面看到的结论是 都会失效

    但是涅 我没仔细研究 :P

  5. 路过一小会[未注册用户]
    *.*.*.*
    链接

    路过一小会[未注册用户] 2009-09-02 15:24:00

    这样的代码,我也见过,每个CategoryLoader实例都会有自己m_categories

  6. known
    *.*.*.*
    链接

    known 2009-09-02 15:24:00

    我看别人是static的,问我为什么,我也不知道~

  7. 老赵
    admin
    链接

    老赵 2009-09-02 15:25:00

    @Todd Wei
    只要有两个线程同时访问一个CategoryLoader对象的GetCategory方法,不也需要考虑线程安全的问题吗?

  8. 老赵
    admin
    链接

    老赵 2009-09-02 15:26:00

    @known
    static与否不重要,重要的是是不是在“同时访问”一个东西。
    实例方法和字段也可以被多个线程同时访问啊。

  9. 拼命占便宜
    *.*.*.*
    链接

    拼命占便宜 2009-09-02 15:28:00

    private void LoadCategories()
    {
    this.m_categories = new Dictionary<int,Category>();
    this.Fill(GetCategoryRoots());
    }
    问题在这里,实例化后,第二个线程进来就不为空啦_

  10. Jun1st
    *.*.*.*
    链接

    Jun1st 2009-09-02 15:40:00

    每一个CategoryLoader实例都有一个自己的m_mutex,锁都是锁自己,不同的Thread访问的不是同一个对象

  11. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-02 15:40:00

    拼命占便宜 说的我五雷轰顶阿 嘎

  12. Todd Wei
    *.*.*.*
    链接

    Todd Wei 2009-09-02 15:41:00

    @拼命占便宜
    有道理

  13. 拼命占便宜
    *.*.*.*
    链接

    拼命占便宜 2009-09-02 15:42:00

    @韦恩卑鄙
    你遭雷劈_

  14. 四有青年
    *.*.*.*
    链接

    四有青年 2009-09-02 15:43:00

    return this.m_categories[id];
    这里有可能相应的键并未添加进字典吧?

  15. mvc后台[未注册用户]
    *.*.*.*
    链接

    mvc后台[未注册用户] 2009-09-02 15:45:00

    mvc做前后台怎么做?route加namespace可以找到/admin/articlecontroller这个的controller,可是view就只能View("~/views/admin/article/index.aspx")?

    有没有简单的办法?

  16. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-02 15:48:00

    拼命占便宜:
    @韦恩卑鄙
    你遭雷劈_


    zhuangbility leads to leipility



    http://msdn.microsoft.com/zh-cn/library/ms998558.aspx
    发现果然clr 对于DCL 有过修正

  17. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-02 15:50:00

    这个故事告诉我们 对于多线城临界区的判断与操作
    一定要做到原子性

  18. HOHOHAHA[未注册用户]
    *.*.*.*
    链接

    HOHOHAHA[未注册用户] 2009-09-02 15:51:00

    Jun1st:每一个CategoryLoader实例都有一个自己的m_mutex,锁都是锁自己,不同的Thread访问的不是同一个对象




    支持一下,就这个了。。

  19. 老赵
    admin
    链接

    老赵 2009-09-02 15:51:00

    @拼命占便宜
    不错不错!

  20. 老赵
    admin
    链接

    老赵 2009-09-02 15:52:00

    @韦恩卑鄙
    其实你给的这个示例里的volatile是不需要的。

  21. HOHOHAHA[未注册用户]
    *.*.*.*
    链接

    HOHOHAHA[未注册用户] 2009-09-02 15:53:00

    要同步的话,就必须大家共享锁,所以 锁要么是静态的,要么类是唯一的。。

  22. 老赵
    admin
    链接

    老赵 2009-09-02 15:53:00

    @Jun1st
    我文章已经说了是多线程访问同一个对象的GetCategory方法,锁自己的mutex又有什么问题呢?

  23. 老赵
    admin
    链接

    老赵 2009-09-02 15:55:00

    HOHOHAHA:要同步的话,就必须大家共享锁,所以 锁要么是静态的,要么类是唯一的。。


    如果只是这个小问题的话,就不值得讨论啦,呵呵。

  24. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-09-02 15:57:00

    Jeffrey Zhao:
    @韦恩卑鄙
    其实你给的这个示例里的volatile是不需要的。


    感觉是写了一个锁的 一个没锁的
    volatile 是垃圾

  25. 鱼蛋
    *.*.*.*
    链接

    鱼蛋 2009-09-02 15:58:00

    private object m_mutex = new object();
    改成
    private static readonly object m_mutex = new object();

  26. known
    *.*.*.*
    链接

    known 2009-09-02 16:02:00

    鱼蛋:
    private object m_mutex = new object();
    改成
    private static readonly object m_mutex = new object();


    private static Dictionary<int, Category> m_categories = new Dictionary<int, Category>();
    
    private void LoadCategories()
    {
        //this.m_categories = new Dictionary<int, Category>();
        this.Fill(GetCategoryRoots());
    }
    

    private void LoadCategories()
    {
        if (this.m_categories == null)
        {
            this.m_categories = new Dictionary<int, Category>();
        }
        this.Fill(GetCategoryRoots());
    }
    

  27. HOHOHAHA[未注册用户]
    *.*.*.*
    链接

    HOHOHAHA[未注册用户] 2009-09-02 16:02:00

    难不成是LoadCategories()方法由于耗时较多,造成数据没有填充完毕时,别的线程来访问。

  28. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-09-02 16:03:00

    check的阀值问题,那么怎么改呢?

    ===============

  29. Jun1st
    *.*.*.*
    链接

    Jun1st 2009-09-02 16:05:00

    Jeffrey Zhao:
    @Jun1st
    我文章已经说了是多线程访问同一个对象的GetCategory方法,锁自己的mutex又有什么问题呢?


    基于这个假设,似乎要考虑LoadCategories()执行时间的问题?第一个Thread还没有执行完,但是this.m_categories已经不是Null了, 然后第二个Thread访问this.m_categories[id]就不存在或者出错?

  30. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-09-02 16:07:00

    完了,我一直都是这么写的…………………………

  31. riccc
    *.*.*.*
    链接

    riccc 2009-09-02 16:08:00

    这段代码,问题太多了

  32. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-09-02 16:09:00

    当执行过 this.m_categories = new Dictionary<int, Category>();
    后,初始化事实上还没做完,但 if (this.m_categories == null) 条件已经不成立了,
    所以会得到一个没的初始化完全的 东东 。

  33. riccc
    *.*.*.*
    链接

    riccc 2009-09-02 16:09:00

    @HOHOHAHA
    老赵说的应该是这个问题

  34. HOHOHAHA[未注册用户]
    *.*.*.*
    链接

    HOHOHAHA[未注册用户] 2009-09-02 16:10:00

    known:

    鱼蛋:
    private object m_mutex = new object();
    改成
    private static readonly object m_mutex = new object();


    改private static Dictionary<int, Category> m_categories = new Dictionary<int, Category>();
    private void LoadCategories()
    {
    //this.m_categories = new Dictiona...




    哎。。楼主都说了一定能保证多个线程访问的类是唯一的了。。所以就不用从这些地方考虑了。

  35. 老赵
    admin
    链接

    老赵 2009-09-02 16:12:00

    @钧梓昊逑
    说的好啊。

  36. 老赵
    admin
    链接

    老赵 2009-09-02 16:12:00

    @DiggingDeeply
    你这个就不是double check,每次只要进lock,影响并发啦。

  37. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-09-02 16:13:00

    应该 lock (m_categories.SyncRoot)

  38. Kevin Dai
    *.*.*.*
    链接

    Kevin Dai 2009-09-02 16:13:00

    lock住m_categories,便不会发生问题了。


    线程A在获取锁后,在执行LoadCategories()方法时,执行完第一个条语句后,如果这个时候有个运气非常佳的线程B进来访问GetCategory(int id)这个方法,发现m_categories不为空(因为已经被线程A实例化过了,虽然没有实质的元素在里面。),这样在执行return this.m_categories[id]是会有问题的。

  39. 老赵
    admin
    链接

    老赵 2009-09-02 16:15:00

    @Kevin Dai
    lock住m_categories直接NullReferenceException了。

  40. 横刀天笑
    *.*.*.*
    链接

    横刀天笑 2009-09-02 16:16:00

    假设,一前一后几个线程来了,假设第一个线程叫做线程A
    线程A碰到第一个null检查,这个时候m_categories还是null,线程A进去了,然后lock(lock把所有其他线程挡在门外,如果有的话),又检查一次,进入LoadCategories方法,执行this.m_categories = new Dictionary<int, Category>();
    这个时候this.m_categories不为null了,但是线程A还有好长的路要走,不过第一个检查的大门已经打开了,如是一大批线程涌进来,但都挡在lock的大门外,Double Check失效了。。。。一点作用都不起

  41. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-09-02 16:17:00

    Jeffrey Zhao:
    @钧梓昊逑
    说的好啊。



    以前写代码的时候也经常写出这种代码来,当时也没觉出来有什么问题,今天老赵这么一说,仔细看了一下,果然问题很大,赶紧去把以前的代码查一遍……

  42. Kevin Dai
    *.*.*.*
    链接

    Kevin Dai 2009-09-02 16:17:00

    @Jeffrey Zhao
    汗……

  43. HOHOHAHA[未注册用户]
    *.*.*.*
    链接

    HOHOHAHA[未注册用户] 2009-09-02 16:18:00

    最简单的方法就是设置一个flag就好了。。
    private bool flag =false;


    if (this.m_categories == null && flag==false)
    {
    lock (this.m_mutex)
    {
    if (this.m_categories == null)
    {
    LoadCategories();
    flag = true;
    }
    }
    }

  44. 老赵
    admin
    链接

    老赵 2009-09-02 16:20:00

    @HOHOHAHA
    加flag可以解决问题,不过用flag就不需要判断this.m_categories是不是等于null了,呵呵。

  45. 横刀天笑
    *.*.*.*
    链接

    横刀天笑 2009-09-02 16:21:00

    其实我还想问:
    这里的m_categories不是static的,GetCategory(int id)也不是static的,一个线程来的时候肯定是一个新的CategoryLoader的实例,会有多个线程同时执行这个GetCategory方法么?(我不确定,望解答)

  46. HOHOHAHA[未注册用户]
    *.*.*.*
    链接

    HOHOHAHA[未注册用户] 2009-09-02 16:22:00

    Jeffrey Zhao:
    @HOHOHAHA
    加flag可以解决问题,不过用flag就不需要判断this.m_categories是不是等于null了,呵呵。



    YES. ^^ 此问题就是说不要Check一些不确定的对象,要用一个常量值来判断

  47. 五斗米
    *.*.*.*
    链接

    五斗米 2009-09-02 16:23:00

    问题应该出在这一句上:
    private object m_mutex = new object();

  48. known
    *.*.*.*
    链接

    known 2009-09-02 16:23:00

    横刀天笑:
    其实我还想问:
    这里的m_categories不是static的,GetCategory(int id)也不是static的,一个线程来的时候肯定是一个新的CategoryLoader的实例,会有多个线程同时执行这个GetCategory方法么?(我不确定,望解答)


    我认为是这么回事,关键是要m_categories实例唯一,不知道理解对不对

  49. 老赵
    admin
    链接

    老赵 2009-09-02 16:26:00

    @横刀天笑
    使用的时候可以保证是在共享同一个CategoryLoader实例就可以啦。
    真要全局唯一,搞个CategoryLoader单例,其是和static是一样的,呵呵。

    我不喜欢static方法,static难以单元测试。
    例如如果我这里要测试CategoryLoader,可以Stub它的GetCategoryRoots方法,然后调用它的GetCategory方法看看是否正确。

  50. 横刀天笑
    *.*.*.*
    链接

    横刀天笑 2009-09-02 16:28:00

    @Jeffrey Zhao
    哦。。。明白了,

  51. 多米诺
    *.*.*.*
    链接

    多米诺 2009-09-02 16:31:00

    看起来没有问题,锚一下.

  52. Avlee
    *.*.*.*
    链接

    Avlee 2009-09-02 16:38:00

    这个问题好像Zhao自己都说过多次了:
    private void LoadCategories()
    {
    Dictionary<int, Category> categories = new Dictionary<int, Category>();
    ....

    this.m_categories = categories;
    }

  53. xiafan
    *.*.*.*
    链接

    xiafan 2009-09-02 16:39:00

    我想说的是。。。我知道这个问题不是锁是否为static引起,但是由于老赵在代码上没写,到处很多朋友误以为老赵说的问题就是static
    于是老赵肯定很无奈吧。。。所以,请先更新下代码吧,这里加不加static是无所谓的,不应该成为讨论的关注点。。。

  54. 老赵
    admin
    链接

    老赵 2009-09-02 16:40:00

    @xiafan
    其实文章里已经写清楚了,估计很多没有没有看清吧。

  55. zellux[未注册用户]
    *.*.*.*
    链接

    zellux[未注册用户] 2009-09-02 16:41:00

    和memory consistency model有关?指令被CLR调度了?俺以前也写过一篇类似的,不过是Java的
    http://techblog.zellux.czm.cn/2008/07/singleton-pattern-and-double-checked-lock/

  56. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-09-02 16:43:00

    @xiafan
    加不加static不是无所谓的
    在博主这个场景下是不能加static的

  57. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-09-02 16:45:00

    加了static,所有实例的m_categories成员初始化都互斥了,而这个显然是没有必要,而且不是设计的初衷,某些情况下也会降低运行效率。

  58. 何睿
    *.*.*.*
    链接

    何睿 2009-09-02 16:46:00

    应该要加volatile关键字的吧

  59. 老赵
    admin
    链接

    老赵 2009-09-02 16:47:00

    @zellux
    这里不是,不过我正打算谈呢,已经快写完了。:)

  60. JimHappy#真嗨皮#郑海滨
    *.*.*.*
    链接

    JimHappy#真嗨皮#郑海滨 2009-09-02 16:48:00

    Avlee:
    这个问题好像Zhao自己都说过多次了:
    private void LoadCategories()
    {
    Dictionary<int, Category> categories = new Dictionary<int, Category>();
    ....

    this.m_categories = categories;
    }



    正确,第二次判断null后new Dictionary<int,Category>应该是个原子操作,还好是c#,要是java的话就必须类似 avlee 的写法了,哪怕是只是个简单的new,因为java的new并不保证一次性完成。

  61. xiafan
    *.*.*.*
    链接

    xiafan 2009-09-02 16:51:00

    @钧梓昊逑
    加了只能说是冗余代码吧,为什么是“不能”,我这样说是为了方便其他的朋友避开误区

  62. xiafan
    *.*.*.*
    链接

    xiafan 2009-09-02 16:52:00

    @Avlee
    应该就是这个改起来最方便了

  63. 狼Robot
    *.*.*.*
    链接

    狼Robot 2009-09-02 16:52:00

    private void LoadCategories()
    {
    var c = new Dictionary<int, Category>();
    this.Fill(c,GetCategoryRoots());
    this.m_categories = c;
    }

    private void Fill(Dictionary<int, Category> c, IEnumerable<Category> categories)
    {
    foreach (var cat in categories)
    {
    c.Add(cat.CategoryID, cat);
    Fill(c, cat.Children);
    }
    }

  64. 程序看客[未注册用户]
    *.*.*.*
    链接

    程序看客[未注册用户] 2009-09-02 16:55:00

    个人意见:

    考虑到CategoryLoader时一个延迟加载的类,且里边的方法都需要实例化。但是m_mutex非静态的,这就意味着每次进来一个线程,实例化一次m_mutex也被实例化一次,在内存中就存在着互不相关的m_mutex实例,似乎没有起到互斥的目的。

    解决方法似乎应该加 static在定义变量m_mutex时候

  65. 鱼蛋
    *.*.*.*
    链接

    鱼蛋 2009-09-02 17:00:00

    private void LoadCategories()
    {
    this.m_categories = new Dictionary<int, Category>();
    this.Fill(GetCategoryRoots());
    }

    private void Fill(IEnumerable<Category> categories)
    {
    foreach (var cat in categories)
    {
    this.m_categories.Add(cat.CategoryID, cat);
    Fill(cat.Children);
    }
    }

    改成

    private void LoadCategories()
    {
    var categories = new Dictionary<int, Category>();
    this.Fill(GetCategoryRoots(), categories );
    this.m_categories = categories;
    }

    private void Fill(IEnumerable<Category> categories, Dictionary<int, Category> dict)
    {
    foreach (var cat in categories)
    {
    dict.Add(cat.CategoryID, cat);
    Fill(cat.Children,dict);
    }
    }

  66. 五斗米
    *.*.*.*
    链接

    五斗米 2009-09-02 17:03:00

    呵呵我明白了,因为this.m_categories不是volatile的,编译器在执行了if (this.m_categories == null)判断后,会直接在return出返回null。

  67. Jason Go
    *.*.*.*
    链接

    Jason Go 2009-09-02 17:05:00

    看了大家的评论,学习到了~~
    double check中被检查的对象应该是不容易产生不确定状态的对象哦
    比如这里就是因为LoadCategories需要时间比较长,而让m_categories有着出入不确定状态的风险。
    第一个check时的条件也没有有效地确保m_categories[id]不会出错~
    改成 if (this.m_categories == null && this.m_categories.ContainsKey(id))呢?

  68. Alvan
    *.*.*.*
    链接

    Alvan 2009-09-02 17:09:00

    if (!this.initialized)
    {
    LoadCategories();
    this.initialized = true;
    }
    另外我很好奇,为什么不在Category初始化的时候就在Loader里登记一下,而要用延迟加载的方式来实现查询?

  69. 老赵
    admin
    链接

    老赵 2009-09-02 17:12:00

    @Alvan
    延迟加载总有它的作用了,呵呵。

  70. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-09-02 17:13:00

    @Jeffrey Zhao
    刚发完一帖还没缓过劲来,这代码一下子没耐心仔细看……罪过 T.T
    不过如果老赵专门说的是double lock的实现问题,不知道所指的是不是局部拷贝的那个解决方案……类似这样:

        public Category GetCategory(int id)
        {
            var cats = this.m_categories;
            if (cats == null)
            {
                lock (this.m_mutex)
                {
                    if (this.m_categories == null)
                    {
                        this.m_categories = cats = new Dictionary<int, Category>();
                        this.Fill(GetCategoryRoots());
                    }
                }
            }
    
            return cats[id];
        }
    

  71. 老赵
    admin
    链接

    老赵 2009-09-02 17:13:00

    @五斗米
    和volatile无关。

  72. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-09-02 17:18:00

    使用有副作用的方法,难免会出现这种问题。。。

        private void LoadCategories()
        {
            var categories = new Dictionary<int, Category>();
            Fill(GetCategoryRoots(), categories);
            m_categories = categories
        }
    
        private void Fill(IEnumerable<Category> categories, Dictionary<int, Category> dict)
        {
            foreach (var cat in categories)
            {
                dict.Add(cat.CategoryID, cat);
                Fill(cat.Children);
            }
        }
    
    

  73. 老赵
    admin
    链接

    老赵 2009-09-02 17:19:00

    @装配脑袋
    脑袋说的好,所以可以避免副作用的地方,的确应该避免。

  74. Alvan
    *.*.*.*
    链接

    Alvan 2009-09-02 17:28:00

    Jeffrey Zhao:
    @Alvan
    延迟加载总有它的作用了,呵呵。



    你的这个例子用到的延迟加载需要一个前提,就是Categories的树结构不能再变更——这个要求对于实际应用来说应该还是很苛刻的。
    如果说为什么不在构造树的同时就构造好Dictionary,我唯一能想到的理由就是这个Category不是由你们来定义的,或者你们无权修改这个类,所以只能用这种贴胶布的方式来加载= =
    我个人还是觉得这个模式是有问题的,因为胶布越多,陷阱越多。

  75. 老赵
    admin
    链接

    老赵 2009-09-02 17:31:00

    @Alvan
    我只是想通过一个例子来说明double check可能会出现的问题。
    我也知道肯定其他问题也有很多,所以从一开始文章最后就补充了一段话……
    事实上Category这些类阿方法都是我随意造出来的,就当一个“理想环境”吧。

  76. Alvan
    *.*.*.*
    链接

    Alvan 2009-09-02 17:32:00

    @Jeffrey Zhao
    哦,呵呵,那是我太较真了 :p

  77. 蔡梓浩
    *.*.*.*
    链接

    蔡梓浩 2009-09-02 17:35:00

    Fill是个耗时操作,应该最后填充完之后再给m_categories赋值。

  78. 请输入您的昵称![未注册用户]
    *.*.*.*
    链接

    请输入您的昵称![未注册用户] 2009-09-02 18:35:00

    实例方法被同时访问,没问题。每个线程自己的字段,有自己的堆栈维护着。如果照楼主这么理解,每个线程的字段可以被任何人同时访问,所有的asp.net代码,都要完蛋了。

    Jeffrey Zhao:
    @known
    static与否不重要,重要的是是不是在“同时访问”一个东西。
    实例方法和字段也可以被多个线程同时访问啊。


  79. DiryBoy
    *.*.*.*
    链接

    DiryBoy 2009-09-02 18:35:00

    double check lock 应该判断目标是否进入了“健康的状态”而不单是非null。宝贵的经验,学习了。

  80. 老赵
    admin
    链接

    老赵 2009-09-02 18:40:00

    @请输入您的昵称!
    真没搞懂你是怎么理解我的话的……

  81. iceboundrock
    *.*.*.*
    链接

    iceboundrock 2009-09-02 19:38:00

    韦恩卑鄙:

    Jeffrey Zhao:
    @韦恩卑鄙
    其实你给的这个示例里的volatile是不需要的。


    感觉是写了一个锁的 一个没锁的
    volatile 是垃圾


    volatile确实是垃圾,除了让代码跑的更慢,对多线程情况下的资源竞争一点帮助都没有。

  82. 老赵
    admin
    链接

    老赵 2009-09-02 19:42:00

    @iceboundrock
    所以一般现在好像大都直接用VolatileRead/Write这样的方法,而不是直接标记为volatile。

  83. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-09-02 20:30:00

    请输入您的昵称!:
    实例方法被同时访问,没问题。每个线程自己的字段,有自己的堆栈维护着。如果照楼主这么理解,每个线程的字段可以被任何人同时访问,所有的asp.net代码,都要完蛋了。

    Jeffrey Zhao:
    @known
    static与否不重要,重要的是是不是在“同时访问”一个东西。
    实例方法和字段也可以被多个线程同时访问啊。




    对象的字段不是存在线程栈上的,多个线程是可以访问同一个对象的同一个字段的。

  84. c b
    *.*.*.*
    链接

    c b 2009-09-02 21:15:00

    请教一下,为什么不能把代码改成这样呢?

    public Category GetCategory(int id)
    {
        //if (this.m_categories == null)
        {
            lock (this.m_mutex)
            {
                if (this.m_categories == null)
                {
                    LoadCategories();
                }
                return this.m_categories[id];
            }
        }    
    }
    
    

  85. 老赵
    admin
    链接

    老赵 2009-09-02 22:04:00

    @c b
    这就不是double check了啊,每次访问都需要被lock,降低性能。

  86. flyingchen
    *.*.*.*
    链接

    flyingchen 2009-09-02 23:32:00

    java中(netbean)开发,写这样的doule-check,会提示重构,重构成

    synchronized(xxx){
    //todo
    }

    这应该类似dotnet中的直接
    lock(xxx){
    //todo
    }

    如果说lock并发有问题,那java为什么有建议这样呢?
    望解答

  87. 老赵
    admin
    链接

    老赵 2009-09-02 23:44:00

    @flyingchen
    Java是因为double check有问题,所以要求把外面的if去掉了,Java的synchronized是没有问题的。
    因为store reordering是不会跨越synchronized的,所以随他怎么重排就都没有问题了。

  88. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-09-03 09:03:00

    xiafan:
    @钧梓昊逑
    加了只能说是冗余代码吧,为什么是“不能”,我这样说是为了方便其他的朋友避开误区


    不是冗余,有和没有static代表的意义是不同的,在某些情况下也会导致不同的运行结果。

  89. 鹤冲天
    *.*.*.*
    链接

    鹤冲天 2009-09-03 12:26:00

    来晚了,不过没看答案,没看回复!说一下我的看法:
    第一个线程,调用GetCategory,执行lock (this.m_mutex),接着执行LoadCategories(),执行完第一句的时候,m_categories就已经不是null了,此时尚未Fill,第二个线程来了,因为m_categories!=null所以直接执行 return this.m_categories[id],但此时m_categories还是空的。

  90. 老赵
    admin
    链接

    老赵 2009-09-03 12:39:00

    @鹤冲天
    你真是喜欢使用感叹号。

  91. 鹤冲天
    *.*.*.*
    链接

    鹤冲天 2009-09-03 12:46:00

    @Jeffrey Zhao
    哈哈!可能是习惯...

  92. googlehacks[未注册用户]
    *.*.*.*
    链接

    googlehacks[未注册用户] 2009-09-03 12:49:00

    老赵把两个统计图X了!

  93. Linker.Lin
    *.*.*.*
    链接

    Linker.Lin 2009-09-24 19:42:00

    感觉这里用DoubleCheck也快不了多少。

  94. 老赵
    admin
    链接

    老赵 2009-09-24 19:52:00

    @Linker.Lin
    double check从来就是为了安全,而不是快。

  95. alby
    *.*.*.*
    链接

    alby 2009-09-25 12:14:00

    单件模式也有类似的问题:
    http://www.yoda.arachsys.com/csharp/singleton.html

  96. 老赵
    admin
    链接

    老赵 2009-09-25 12:58:00

    @alby
    单件模式自然很容易用到double check。

  97. a3xing
    59.39.253.*
    链接

    a3xing 2011-10-16 20:13:38

    请楼主仔细看看自己的代码,错的可以了!不能说是double check惹的祸。

  98. 老赵
    admin
    链接

    老赵 2011-10-24 19:50:13

    @a3xing

    那你说说错在什么地方吧。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我