从抽象类创建线程安全类

Posted

技术标签:

【中文标题】从抽象类创建线程安全类【英文标题】:Making a thread safe class from an abstract class 【发布时间】:2019-03-16 15:38:58 【问题描述】:

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

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

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();
      
    

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

【问题讨论】:

通常这是通过封装而不是继承来完成的:referencesource.microsoft.com/#mscorlib/system/collections/… 你有相同数量的代码要编写,但至少你不必将所有基本方法都标记为虚拟 当然可以,但是它们之所以是虚拟的,是因为其他一些类可以修改基类的行为,所以它们需要保持虚拟。 如果 proc 只访问实例成员,我也会将锁对象设为实例对象,否则您将不必要地锁定不会相互干扰的调用。即,如果同时调用 a.proc1()b.proc1() 就可以了。 【参考方案1】:

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

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

作为一般建议:

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

【讨论】:

【参考方案2】:

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

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

【讨论】:

以上是关于从抽象类创建线程安全类的主要内容,如果未能解决你的问题,请参考以下文章

java基础——线程的创建和状态

Java多线程:实现Runnable接口创建线程方式详解

多线程:线程相关类

如何创建线程?如何保证线程安全?

无法从抽象类中读取文件

如何创建线程?如何保证线程安全?