zoukankan      html  css  js  c++  java
  • 著名的双检锁技术

    最近公司的项目中发现一个编译优化导致的bug。同事叙述为CPU开启out-of-order execution优化时,是有bug的”。针对这个问题,比较好的优化方法如下:

            private static JobManager self;
            private static object asyncObj = new object();
    
            public static JobManager Instance
            {
                get
                {
                    if (self == null)
                    {
                        lock (asyncObj)
                        {
                            if (self == null)
                            {
                                // 正确的实现方法应该为: var temp = new JobManager(); Interlocked.Exchange(ref self, temp);
                                self = new JobManager();
                            }
                        }
                    }
                    return self;
                }
            }
    

    这里需要解释一下:

    self = new JobManager()

    这句你的本意是为 JobManager 分配内存,调用构造器初始化字段,再将引用赋给 self ,即发布出来让其他线程可见。但是,那只是你一厢情愿的想法,编译器可能这样做:为JobManager 分配内存,将引用发布到(赋给)self,再调用构造器。然而,如果在将引用发布给 self 之后,调用构造器之前,另一个线程发现 self 不为 null,便开始使用JobManager对象,这时会发生什么?这个时候对象的构造器还没有执行结束!这是一个很难追踪的bug。

    从双检锁技术的角度来看,使用Interlocked.Exchange确实是最好的解决方案。但有两个问题,它该如何解决?
    1.速度是否够快?
    2.如果一个线程池线程在Monitor的线程同步构造上阻塞,线程池会创建另一个线程来保持CPU的“饱和”,而创建一个新线程的代价是很昂贵的,我们该如何避免这样的情况?
     
    试着跳出“lock+2次if”的框子,我们可以使用Interlocked.CompareExchange来解决上面的问题。下面是一个示例:
        internal sealed class MySingleton
        {
            private static MySingleton s_value = null;
            public static MySingleton GetMySingleton()
            {
                if (s_value != null) return s_value;
     
                MySingleton temp = new MySingleton();
                Interlocked.CompareExchange(ref s_value, temp, null);
                return s_value;
            }
        }
    

    虽然多个线程同时调用GetMySingleton,会创建2个或者更多的MySingleton对象,但没有被s_value引用的临时对象会在以后被垃圾回收。大多数应用程序很少会发生同时调用GetMySingleton的情况,所以不太可能出现创建多个MySingleton对象的情况。上述代码带来优势是很明显的,首先,它的速度是非常快,其次,它永不阻塞线程。这就解决了前面在双检锁技术中提出的问题。

     
    另外,在.net 4.0中提供了2个类型封装上述两种模式(双检锁技术、使用Interlocked.CompareExchange技术):
    泛型System.Lazy类和System.Threading.LazyInitializer类。下面是2个示例:
            public static void Main()
            {
                Lazy<string> s = new Lazy<string>(() => DateTime.Now.ToLongTimeString(), LazyThreadSafetyMode.PublicationOnly);
    
                Console.WriteLine(s.IsValueCreated);
                Console.WriteLine(s.Value);
                Console.WriteLine(s.IsValueCreated);
                Thread.Sleep(5000);
                Console.WriteLine(s.Value);
                Console.WriteLine(DateTime.Now.ToLongTimeString());
            }
    
     
    输出结果:

            public static void Main()
            {
                string name = null;
    
                LazyInitializer.EnsureInitialized(ref name, () => "Benjamin");
                Console.WriteLine(name);
     
                LazyInitializer.EnsureInitialized(ref name, () => "Yao");
                Console.WriteLine(name);
            }
    
    输出结果:

    其中枚举LazyThreadSafetyMode解释如下:
        public enum LazyThreadSafetyMode
        {
            None = 0,     //完全没有线程安全劫持(适合GUI应用程序)
            PublicationOnly = 1,      //使用Interlocked.CompareExchange技术
            ExecutionAndPublication = 2,     //使用双检锁技术
        }
    
  • 相关阅读:
    Java实现 LeetCode 802 找到最终的安全状态 (DFS)
    Java实现 LeetCode 802 找到最终的安全状态 (DFS)
    Java实现 LeetCode 802 找到最终的安全状态 (DFS)
    Java实现 LeetCode 804 唯一摩尔斯密码词 (暴力)
    Java实现 LeetCode 803 打砖块 (DFS)
    Java实现 LeetCode 804 唯一摩尔斯密码词 (暴力)
    Java实现 LeetCode 803 打砖块 (DFS)
    Java实现 LeetCode 804 唯一摩尔斯密码词 (暴力)
    英文标点
    post sharp 与log4net 结合使用,含执行源码 转拷
  • 原文地址:https://www.cnblogs.com/MikeYao/p/2584424.html
Copyright © 2011-2022 走看看