从抽象类创建线程安全类



一个设计问题。我知道这种方法是线程安全的,但从设计的角度来看,有没有更好的方法可以做到这一点?

我有一个抽象类(不是线程安全的):

public abstract class Class1
{
protected someobject myobject;
public Class1()
{
myobject = new someoject();
}
public virtual void proc1()
{
// do something with my object
}
public virtual void proc2()
{
// do something with my object
}
public virtual void proc3()
{
// do something with my object
}
}

现在我想从这个需要线程安全的类中创建一个后代,所以我这样做:

public class Class2: Class1
{
private static readonly object obj = new object();

public override void  proc1()
{
lock(obj)
{
base.proc1();
}
}
public override void proc2()
{
lock(obj)
{
base.proc2();
}
}
public override void proc3()
{
lock(obj)
{
base.proc3();
}
}
}

我可以使基线程安全,但我有一些其他类继承自相同的基线程并且不需要线程安全,所以我不想强制线程安全进入其中。这种设计有什么问题吗?如果基地有很多公共成员,那就有点乏味了......

如果你想以线程安全的方式使用Class1(或与此相关的后代类),你应该使用封装而不是继承,正如Kevin Gosse所说。继承不应该以这种方式使用,因为如果Class1有更多非虚拟(甚至可能是公共)的方法,这些方法将更改对象的内部状态,您将无法控制它们。您应该获取并封装一个继承Class1的类,然后公开将作为线程安全方法调用的方法。

即使你确实控制了Class1设计,每次添加或更改Class1的方法时都考虑线程安全继承者(Class2)也是一个糟糕的设计。

很难推断出代码的潜在用途,但从一般考虑的角度来看,我会强调以下问题:

  1. 责任。我正在查看Class2代码,发现除了基类方法的竞争条件保护外,与基类相比,它没有做任何其他事情。通常,我们倾向于在特定状态方面施加线程安全性,以确保并发访问条件下的状态一致性。但在这种情况下,Class2根本不知道它保护的行为是否会导致竞争条件。如果在不再需要线程安全时修改Class1怎么办 - 我们将在Class2类中使用冗余锁(或者将删除它们与Class1具有这种间接耦合)。如果Class1使用其他方法进行扩展,或者更糟糕的是,有人决定通过另一个锁对象将额外的线程安全添加到Class1中(在最坏的情况下,我们可能会有死锁),该怎么办?因此,每次我们在Class1中进行此类更改时,我们还必须检查Class2代码以确保没有任何损坏,换句话说,我们仅在这些类之间紧密耦合,只是因为Class2有责任不应该属于它。
  2. LSP。当我们谈论类的层次结构时,我们通常会记住,无论使用哪种类型的层次结构,层次结构合同的使用都不应该有不同的要求。在层次结构中具有线程安全类而不是线程安全类会对此层次结构的使用施加其他限制。特别是,使用者应该知道它在什么情况下处理什么类型的实例,这可能会排除 LSP 兼容层次结构可以使用的方案数。例如,使用者将无法在通用方案中使用Class1集合,除非它明确知道方案是线程安全的。

作为一般建议:

  1. 我会尽量避免在子类中引入可能取决于可以使用子类的上下文的行为。我会尝试使整个层次结构保持一致:层次结构中的所有类要么都是线程安全的,要么它们都不是。
  2. 如果层次结构中的某些类
  3. 需要线程安全,而某些类不需要,则可能是层次结构协定内聚性较低的指标。我会尝试将基类和子类分解为更小的部分,这可能意味着多个合约和可能的层次结构。
  4. 如果基类或其他类保留了可能在不同并发上下文中使用的状态,并且从线程安全的角度来看仍然难以实现同构层次结构,我会考虑将同步逻辑移到层次结构中的类之外,并将此责任留给使用者。

超类(抽象或其他)中的代码在 s 子类(扩展)中不是线程安全的。 请改用封装。

另请注意,如果要对类进行更改,则可以在同步方法中执行此操作(如下面的get和sets,但更复杂)。

最好使class1和class2不可变,并在它们更改时替换它们。

封装(想想豆子)

public class{
Class1 class1;
Class2 class2
public synchronized set1(Class1 class1){
this.class1 = class1;
}
public A synchronized get1(){
return class1;
}
public synchronized set2(Class2 class2){
this.class1 = class2;
}
public A synchronized get2(){
return class2;
}
}

最新更新