【问题标题】:Will using a static method affect performance when running the same method in parallel?并行运行相同方法时,使用静态方法会影响性能吗?
【发布时间】:2021-10-01 06:51:40
【问题描述】:

我有一个 util 类,带有一个私有构造函数,并且所有方法都是静态的。原因是,没有一个方法共享任何对象,因为该类不存储任何状态。但是我想知道如果这些静态方法并行调用,是否会导致任何请求阻塞?静态变量存储在共享内存位置这一事实是否会影响这一点? 对于理解底层执行机制的任何帮助将不胜感激。

下面是我正在谈论的课程。

public final class PersonUtil {
    private static IPerson person;

    private PersonUtil() {
    }

    public static PersonOutput generatePerson(final PersonInput personInput) {
        if (isParamProvided()) {
            person = new MyPerson();
        } else {
            person = new LegacyPerson();
        }
        return person.create(personInput);
    }

    public static void assignState(final String guid) {
      //call AssignState API for given guid
    }

    public static String getGuidForId(final String id) {
        //call GetGuid API for the given ID
    }

    public static String getPersonNameByGuid(final String guid) {
        // call DescribePerson API
    }
}

【问题讨论】:

  • 这些方法是否修改静态变量?如何确保线程安全?
  • 我用我的班级更新了这个问题,它们都是不相关的方法,它们不会修改任何静态变量。他们都使用给定的输入参数进行自己的 API 调用
  • 在您的示例中,这将取决于这些对这些 API 的请求是如何实现的——它们是否使用某种共享套接字?会话处理是如何实现的?等等。但基本上,static 在这方面没有任何改变,同样的考虑也适用于实例方法。
  • 给出的例子根本不应该是静态的,或者至少不应该有一个静态字段person
  • 你真的觉得发public static void methodName(final String guid) { //call MethodName API for given guid }这样的代码有用吗?我们应该对评论的线程安全性或您的问题中未包含的代码进行推理吗?

标签: java static


【解决方案1】:

是的,你在这里调用generatePerson时可能会遇到并发问题,因为这个方法会影响静态字段person

假设你有两个线程:

  • 使用导致person = new MyPerson() 的参数调用generatePerson
  • 另一个调用generatePerson 的参数导致person = new LegacyPerson()

如果两个线程都是并发的,那么可能会发生:

  1. 第一个线程集person = new MyPerson()
  2. 设置person = new LegacyPerson() 之后的第二个线程
  3. 第一个线程在person 上调用create(),即LegacyPerson(因为线程2 在前面的语句中设置了它),但它应该在MyPerson 的实例上调用它李>

为避免这种情况,您应该将person 声明为volatile(这将确保所有线程在读取变量时看到相同的内容),然后您应该将generatePerson 声明为同步,这样一次只能访问一个线程:

private static volatile IPerson person;

public static synchronized PersonOutput generatePerson(final PersonInput personInput) {
    if (isParamProvided()) {
        person = new MyPerson();
    } else {
        person = new LegacyPerson();
    }
    return person.create(personInput);
}

如果MyPersonLegacyPerson 不保持任何状态,则可能会有更好的解决方案。如果是这种情况,那么您可以将两个实例声明为final

private static final IPerson myPerson = new MyPerson();
private static final IPerson legacyPerson = new LegacyPerson();

所以你改变 createPerson 方法来做到这一点:

public static PersonOutput createPerson(final PersonInput personInput) {
    if (isParamProvided()) {
        return myPerson.create(personInput);
    } else {
        return legacyPerson.create(personInput);
    }
} 

这样,您永远不会同时编写静态字段,因此您的静态方法与并发问题无关。

注意:我在createPerson 上提供示例,因为它是您显示代码的唯一方法。您应该在所有 API 端点上应用相同的逻辑,而不会看到我们无法知道是否存在其他并发问题的代码。

建议:尽可能避免并发。它始终是不稳定问题的根源,仅在绝对必要时才应使用。在我提供的第二个示例中,您正在避免它,请尝试在其他端点中执行相同的操作。

【讨论】:

  • 在问题的代码中,person 永远不会被读取,除非在刚刚写入它的方法中。因此,更好的选择是将person 设为方法本身内的局部变量。当然,除非问题中未显示对person 的其他访问权限。在这种情况下,代码仍然会被破坏,因为声明变量 volatile 不足以防止出现竞争条件。
  • @Holger person 用于从输入创建输出。 person 的实例可以是其中之一。所以是的,它被读取了,即使没有像 getter 那样明确地读取。是的,volatile 可能不是这里最好的解决方案,我之后马上提出了另一个解决方案。
  • 我已经说过,它“从不读取,在刚刚写入它的方法中除外”。最简单的解决方法是将person 转换为局部变量,无论MyPersonLegacyPerson 保持状态、可变还是线程安全,它都可以工作。实际上,您甚至根本不需要变量:return (isParamProvided()? new MyPerson(): new LegacyPerson()) .create(personInput);
  • @Holger 当然,这是另一种选择。但是您每次被调用时都会创建一个新实例。使用我上面提出的解决方案,您只需静态创建一次并每次重用它。当然,假设类没有状态。
  • 每次调用该方法时创建一个新实例,这正是原始代码所做的,它只是通过共享变量不必要地插入了竞争条件。通过使用局部变量,在不改变原始程序逻辑并且不需要关于其他类的假设的情况下消除了竞争条件。我们不知道缓存对象是否可行(即它们是否是线程安全的),也不知道它是否真的有必要(即它们的构造是否昂贵)。
【解决方案2】:

您不会遇到性能问题。如果您没有synchronized 块或synchronized 方法,则不会有任何性能问题。

您很可能会遇到并发问题。如果不同线程同时生成多个人,则只会将一个人分配到该字段,您将无法预测女巫。

更糟糕的是,被分配的人可能会随着时间的推移而被替换,而没有任何明确的分配(见下一段)。

您肯定会遇到可见性问题。如果一个线程分配了静态字段person,那么它在某个时间(可能是微秒或分钟)对另一个线程不可见。将人员定义为volatile 以避免它。

private static volatile IPerson person;

当然,您必须确保 MyPersonLegacyPerson 类是线程安全的。这两个类的所有字段必须是finalvolatile。如果无法达到,您将需要 synchronized 块,包括相关的性能缺陷。

【讨论】:

  • 的确如此,但在这个例子中,问题不仅在于读取人的价值,还在于使用它。如果一个线程将 person 设置为某个值,然后另一个线程在没有设置自己的情况下使用它,则它可能使用了错误的 person 实例,因此 person 易失性这一事实并不能真正避免并发问题。
  • 我建议对人的初始化只发生一次。否则整个代码没有意义...
  • 当您假设person 的初始化应该只发生一次,您需要一种机制来确保该属性。声明变量volatile 不会。另一方面,如果您有一种机制可以保证person 被初始化一次,那么该机制可能会使volatile 变得不必要。
  • 是否需要volatile,取决于你用来确保变量只初始化一次的机制。你没有说出一个名字,所以你不能做出任何假设。但是,正如您所说,final 将是一种选择。另一种是任何外部同步机制。例如,保持初始化状态的 AtomicBooleanSemaphore。或者只是制作generatePerson 方法synchronized。这些选项都不需要 personvolatile
  • 绝对不简单。有一个原因,语言规范有一个关于该主题的long chapter。半真半假和编造的规则在这里无济于事。我不在乎您是否不知道何时可以省略 volatile 修饰符,如果您愿意,可以将其覆盖在所有代码中。但至少你必须明白,当volatile 没有帮助时。
猜你喜欢
  • 2014-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-04
  • 1970-01-01
  • 1970-01-01
  • 2012-05-05
  • 1970-01-01
相关资源
最近更新 更多