我是否需要在Singleton类中使每个方法同步?

问题描述 投票:0回答:4

在下面的多线程编程示例中,Replacer类可以同时被多个线程访问,因此我使类单例并使getInstance()方法同步。我是否还需要使replaceNum()方法同步,假设该方法也将由多个线程调用?

public class Replacer {

  private static Replacer replacer = null;
  private List<Integer> nums;

  private Replacer() {
    nums = new ArrayList<>();
  }

  public static synchronized Replacer getInstance() {
    if (replacer == null) {
      replacer = new Replacer();
    }

    return replacer;
  }

  // Do I need to make below method synchronized?
  public void replaceNum(List<Integer> newNums) {
    if (nums.size() > 0) {
      nums.remove(nums.size() - 1);
    }

    nums.addAll(newNums);
  }
}
java singleton synchronized
4个回答
4
投票

对于单身人士来说,这条规则并不特别。这纯粹是:

  • 该方法是否需要支持多个调用它的线程,以及
  • 如果多个线程在Just The Wrong Time调用它,它会做什么失败

你的replaceNum的答案是“是的,它需要同步”(并且在方法级别是有意义的,因为它内部的所有内容都需要同步)因为它使用ArrayList的方法,这不是线程安全的。作为the Javadoc says

请注意,此实现不同步。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。 (结构修改是添加或删除一个或多个元素的任何操作,或显式调整后备数组的大小;仅设置元素的值不是结构修改。)

所以该方法需要同步访问该ArrayList

鉴于这样的一揽子声明,你必须假设这些方法都不是线程安全的,除非它明确说明它是。 (同样,除非一个类明确表示它是线程安全的,否则你必须假设它不是。)


1
投票

(答案是为an earlier version of the question写的)。

不,您不需要使其同步,因为您从不在结构上更改列表。

如果列表不为空,则只添加到列表中。所以你永远不会添加第一个元素。

这几乎肯定是一个逻辑缺陷,但它回答了所写的问题。

假设您修复了逻辑,并且可以向列表添加元素,并且您想要从多个线程调用它,是的,它应该是同步的。

但其原因并不是ArrayList明确记录为从多个线程访问时需要同步(尽管表明必须以某种方式涉及同步):

请注意,此实现不同步。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。 (结构修改是添加或删除一个或多个元素的任何操作,或显式调整后备数组的大小;仅设置元素的值不是结构修改。)

这是您在列表上执行复合操作:如果列表不为空,则删除其最后一个元素;然后添加一个新元素。

第一个位(如果列表不为空,删除其最后一个元素)可能被多个线程弄乱。例如,两个线程可以同时找到列表大小为1;然后一个线程将另一个元素移到另一个元素之前;然后另一个线程试图删除一个现在删除的元素。 KABOOM。

或者,如果两个线程都发现列表为空,则添加可能会混乱,因此不要尝试删除任何内容;然后两者都添加,你最终得到列表中的两个元素。

即使您使用Collections.synchronizedList,如ArrayList文档中的建议,这也不能解决您的问题。重要的是要认识到上面的引用是你必须做的,以保持ArrayList的不变量;它不会强制执行代码的不变量。

您需要能够以原子方式执行此逻辑。整个过程就像一个“单一动作”,由一个独立访问列表的线程完成。这是通过同步完成的。


0
投票

任何更改共享实例状态的方法都应标记为synchronized以确保安全。 synchronized方法“检出”对类的实例(this)的锁定,并且所有同步方法都控制同一个锁,因此在多个方法上使用此关键字将完成这项工作。虽然,如果你希望它被多次调用,你应该尝试在一小段代码上使用synchronize。通常,您应该尽可能少地同步代码以获得最佳性能。


0
投票

如果对方法调用(写入和读取)有多次访问,那么,是的,您需要使用synchronized,因为ArrayList本身并不是线程安全的实现。

就像是:

public synchronized void replaceNum(List<Integer> newNums) {
    if (nums.size() > 0) {
        nums.remove(nums.size() - 1);
    }
    nums.addAll(newNums);
}

另一个选择是你可以使用Vector代替,这使你的实现线程安全。但是,它有自己的性能问题。通常,与非线程安全的对应程序相比,线程安全实现的性能较慢。

© www.soinside.com 2019 - 2024. All rights reserved.