使自定义对象线程安全

本文关键字:安全 线程 对象 自定义 | 更新日期: 2023-09-27 17:54:31

我之前发布了一个关于返回集合的问题,并且出现了线程安全的主题。我得到了这个链接来做更多的阅读,我发现了这一行:

一般来说,避免锁定公共类型或超出您的代码的控制。

首先,纠正我,如果我错了,但不是微软给锁上一个公共类型的例子,平衡变量?

其次,如何锁定自己的getter/setter属性。假设我有以下类:

private int ID;
public Person(int id)
{
    this.Identification= id;
}
public int Identification
{
    get { return this.ID; }
    private set
    {
        if (value == 0)
        {
            throw new ArgumentNullException("Must Include ID#");
        }
        this.ID = value;
    }
}

getter是公共的,对吗?只有setter被声明为私有。那么,如何锁定或使getter/setter属性线程安全呢?

使自定义对象线程安全

你应该在Person类中定义一个像这样的变量

private readonly object _lock_ = new Object();

如果您想在Person的所有实例上进行同步,您应该将其设置为static

那么当你想要锁定时,你可以这样做

lock(_lock_) //whose there? it's me, I kill you! oops sorry that was knock knock
{
    //do what you want
}

我建议你阅读这篇文章:1

当您需要锁定一个变量时,您需要锁定使用该变量的每个位置。锁不是针对变量的,而是针对使用变量的代码区域的。

如果你只在一个地方"读"并不重要——如果你需要为一个变量锁定,你需要在使用该变量的所有地方锁定。

lock的替代方案是Interlocked类——它使用处理器级原语进行锁定,速度更快。但是,Interlocked不能保护多个语句(并且有两个Interlocked语句与在单个lock中有这两个语句是不一样的)。

在进行锁定时,必须锁定引用类型的实例(在大多数情况下(但并非总是),该实例也应该是静态实例)。这是为了确保所有锁实际上都是在相同的实例上取出的,而不是它的副本。显然,如果你在不同的地方使用一个副本,你就没有锁定相同的东西,所以你的代码将无法正确序列化。

例如:

private static readonly object m_oLock = new object ();
...
lock ( m_oLock )
{
    ...
}

使用非静态锁是否安全需要对代码进行详细的分析——在某些情况下,它会导致更多的并行性,因为代码的同一区域被锁定的次数较少,但对它的分析可能非常棘手——如果你不确定,就使用static锁对象。获取一个打开的锁的成本是最小的,但是不正确的分析可能会导致需要花费很长时间来调试的错误。

编辑:

下面的例子展示了如何锁定属性访问:

private int ID; // do NOT lock on value type instances
private static readonly object Lock = new object ();
public Person(int id)
{
    this.Identification = id;
}
public int Identification
{
    get
    { 
        lock ( Lock )
        {
            return this.ID;
        }
    }
    private set
    {
        if (value == 0)
            throw new ArgumentNullException("Must Include ID#");
        lock ( Lock )
        {
            this.ID = value;
        }
    }
}

因为你的属性只做一个微不足道的get/set操作,你可以尝试使用Interlocked.CompareExchange而不是一个完整的锁-它会使事情稍微快一些。但是请记住,联锁操作与锁不同。

编辑2:

还有一件事:int上的一个简单的get/set操作不需要锁——读写32位的值(本身)都是原子的。所以这个例子太简单了—只要您不尝试在应该以原子方式完成的多个操作中使用ID,那么就不需要锁。但是,如果您的实际代码实际上更复杂,需要检查和设置ID,那么您可能需要锁定和,您需要锁定组成原子操作的所有操作。这意味着你可能不得不从getter/setter中取出锁——一个变量的get/set对上的两个锁与它们周围的单个锁是不一样的。

关于微软文章的第一个问题的答案:不。本文不锁定balance变量。它锁定私有thisLock变量。所以这个例子很好。

其次,根据您发布的代码,您不需要添加任何锁来使您的类线程安全,因为您的数据是不可变的。一旦您创建了Person的实例并在构造函数中设置了Identification属性的值,您的类设计就不允许再次更改该属性。这就是不可变性,它本身提供了线程安全性。因此,您不需要费心添加锁之类的东西。同样,假设您的代码示例是准确的。

编辑:这个链接可能对你有用