假设我有以下类:
public class IntBagWithLock
{
private readonly lockObject = new object();
private bool assigned = false;
private int data1;
private int data2;
public int? Data1
{
get { lock (lockObject) { return assigned ? data1 : (int?)null; } }
}
public int? Data2
{
get { lock (lockObject) { return assigned ? data2 : (int?)null; } }
}
public bool Assigned { get { lock(lockObject) { return assigned; } }
public bool TrySetData(int value1, int value2)
{
lock (lockObject)
{
if (assigned) return false;
data1 = value1;
data2 = value2;
assigned = true;
return true;
}
}
public bool IsEquivalentTo(IntBagWithLock other)
{
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(other, null)) return false;
lock (lockObject)
{
if (!assigned) return false;
lock (other.lockObject)
{
return other.assigned && other.data1 == data1 && other.data2 == data2;
}
}
}
}
这里我担心的问题是,由于IsEquivalentTo
的实现方式,如果一个线程调用了item1。IsEquivalentTo(item2)
并获取了item1
的锁,另一个调用了item2。IsEquivalentTo(item1)
并获取了item2
。
我应该做些什么来尽可能确保这种僵局不会发生?
更新2:代码示例已被修改为更接近我实际拥有的内容。我认为所有答案仍然有效。
通常你给每个对象一个唯一的ID,然后从低id锁定到高id:
public class BagWithLock
{
// The first Id generated will be 1. If you want it to be 0, put
// here -1 .
private static int masterId = 0;
private readonly object locker = new object();
private readonly int id = Interlocked.Increment(ref masterId);
public static void Lock(BagWithLock bwl1, BagWithLock bwl2, Action action)
{
if (bwl1.id == bwl2.id)
{
// same object case
lock (bwl1.locker)
{
action();
}
}
else if (bwl1.id < bwl2.id)
{
lock (bwl1.locker)
{
lock (bwl2.locker)
{
action();
}
}
}
else
{
lock (bwl2.locker)
{
lock (bwl1.locker)
{
action();
}
}
}
}
}
你像这样使用它:
bool equals;
BagWithLock(bag1, bag2, () => {
equals = bag1.SequenceEquals(bag2);
});
因此,您传递了一个Action
,其中包含您想在锁
中执行的操作。
静态master Id
上的Interlock.增量
保证了每个类都有一个唯一的id
。请注意,如果您创建了该类的40亿多个实例,就会出现问题。如果需要这样做,请使用long
。
由于OP提到的data
是Immutable
,我认为这里根本不需要锁,“易失性”应该可以做到这一点。
public class BagWithLock
{
private volatile object data;
public object Data
{
get { lock return data; }
set { data = value; }
}
public bool IsEquivalentTo(BagWithLock other)
{
return object.Equals(data, other.data);
}
}
这应该是线程安全的。如果我错了,请纠正我。
我不知道你为什么每次都锁,但是你可以这样做:
public bool IsEquivalentTo(BagWithLock other)
{
object myData;
object otherData;
lock (lockObject)
myData = data;
lock (other.lockObject)
otherData = other.data;
return object.Equals(myData, otherData);
}
这样,项目在比较时不会改变。
一般来说,这种锁有一些缺点,我想我会做一个通用的静态lockObject
,这样你一次只能在一个可能是竞争条件的方法中拥有on object
根据您的更新更新,我会说您应该使用:
private static readonly object equalLock = new object();
public bool IsEquivalentTo(IntBagWithLock other)
{
lock(equalLock){
if (ReferenceEquals(this, other)) return true;
if (ReferenceEquals(other, null)) return false;
if (!assigned) return false;
return other.assigned && other.data1 == data1 && other.data2 == data2;
}
}