【问题标题】:Instance constructor sets a static member, is it thread safe?实例构造函数设置一个静态成员,它是线程安全的吗?
【发布时间】:2008-09-03 14:28:57
【问题描述】:

我正在重构一些代码,并且想知道在实例构造函数中使用lock

public class MyClass {

    private static Int32 counter = 0;
    private Int32 myCount;

    public MyClass() {

        lock(this) {
            counter++;
            myCount = counter;
        }
    }
}

请确认

  1. 实例构造函数是线程安全的。
  2. lock 语句阻止访问该代码块,而不是静态“计数器”成员。

如果原始程序员的意图是让每个实例都知道它的“计数”,我将如何同步对“计数器”成员的访问以确保另一个线程不是新的 MyClass 并更改在这个设置计数之前计数?

仅供参考 - 这个类不是单例。实例必须知道它们的数量。

【问题讨论】:

    标签: c# .net multithreading thread-safety


    【解决方案1】:

    如果你只是增加一个数字,有一个特殊的类(互锁)就是为了……

    http://msdn.microsoft.com/en-us/library/system.threading.interlocked.increment.aspx

    Interlocked.Increment 方法

    递增指定变量并将结果存储为原子操作。

    System.Threading.Interlocked.Increment(myField);
    

    有关线程最佳实践的更多信息...

    http://msdn.microsoft.com/en-us/library/1c9txz50.aspx

    【讨论】:

      【解决方案2】:

      我猜这是一个单例模式或类似的东西。您要做的不是锁定对象,而是在修改计数器时锁定计数器。

      private static int counter = 0;
      private static object counterLock = new Object();
      
      lock(counterLock) {
          counter++;
          myCounter = counter;
      }
      

      因为您当前的代码有点多余。特别是在构造函数中,只有一个线程可以调用构造函数,这与可以跨线程共享并从任何共享线程访问的方法不同。

      从我可以从您的代码中得知,您正试图在创建对象时为其提供当前计数。因此,使用上面的代码,计数器将在本地更新和设置时被锁定。所以所有其他构造函数都必须等待计数器被释放。

      【讨论】:

      • 构造函数内部的'锁'没用?我需要在静态方法中锁定对静态“计数器”的访问,否则我无法确定还有谁在其他地方读/写它。
      • @anthony:哪个锁没用? this 上的锁定是无用的,因为没有其他人可以引用 this 来锁定它。 counterLock 上的锁定(见上)是正确的。
      【解决方案3】:

      @ajmastrean

      我并不是说你应该使用单例模式本身,而是采用它封装实例化过程的方法。

      • 将构造函数设为私有。
      • 创建一个返回类型的静态实例方法。
      • 在静态实例方法中,在实例化之前使用lock关键字。
      • 实例化该类型的新实例。
      • 增加计数。
      • 解锁并返回新实例。

      编辑

      我遇到了一个问题,如果计数下降了,您怎么知道? ;)

      再次编辑

      考虑一下,您可以将代码添加到调用另一个静态方法以减少计数器的析构函数中:D

      【讨论】:

      • 递减计数器会导致数字被分发两次。我认为原始程序员的意图是在“创建时”保持“总”计数。该计数似乎并未用于跟踪有关“当前实例”的任何内容。
      【解决方案4】:

      您可以使用另一个静态对象来锁定它。

      private static Object lockObj = new Object();
      

      并在构造函数中锁定这个对象。

      lock(lockObj){}
      

      但是,我不确定是否有一些情况需要处理,因为 .NET 中的编译器优化就像 java 的情况一样

      【讨论】:

      • 实际上这是该示例中最好的部分。 ctor 中的 lock(this) 非常好,因为“this”还没有返回给其他任何人,所以发生死锁的范围为零。
      • @Quibblesome 但它不起作用,因为它代表问题。如果两个线程同时运行new MyClass(),一个线程将锁定对其实例的引用,另一个线程将锁定其实例。由于它们是不同的引用,锁不会锁定任何东西,线程不会互相等待,增量可能会出错。
      【解决方案5】:

      最有效的方法是使用互锁递增操作。它将递增计数器并立即(原子地)返回静态计数器的新设置值

      class MyClass {
      
          static int _LastInstanceId = 0;
          private readonly int instanceId; 
      
          public MyClass() { 
              this.instanceId = Interlocked.Increment(ref _LastInstanceId);  
          }
      }
      

      在您的原始示例中,lock(this) 语句不会产生预期的效果,因为每个单独的实例都有不同的“this”引用,因此多个实例可能会同时更新静态成员。

      在某种意义上,构造函数可以被认为是线程安全的,因为在构造函数完成之前对正在构造的对象的引用是不可见的,但这对于保护静态变量没有任何好处。

      (Mike Schall 先有互锁位)

      【讨论】:

      【解决方案6】:

      我认为如果你修改Singleton Pattern 以包含一个计数(显然使用线程安全方法),你会没事的:)

      编辑

      我不小心删了!

      我不确定实例构造函数是否线程安全的,我记得在设计模式的书中读到过这个,你需要确保在实例化过程中锁到位,纯粹是因为这个..

      【讨论】:

        【解决方案7】:

        @Rob

        仅供参考,此类可能不是单例,我需要访问不同的实例。他们必须简单地保持计数。您会更改单例模式的哪一部分来执行“计数器”递增?

        或者您是否建议我公开一个静态方法来构造阻止访问递增和读取带有锁的计数器的代码。

        public MyClass {
        
            private static Int32 counter = 0;
            public static MyClass GetAnInstance() {
        
                lock(MyClass) {
                    counter++;
                    return new MyClass();
                }
            }
        
            private Int32 myCount;
            private MyClass() {
                myCount = counter;
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2010-09-05
          • 2011-01-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-12-10
          • 1970-01-01
          相关资源
          最近更新 更多