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

时间:2018-09-11 17:22:19

标签: java singleton synchronized

在下面的多线程编程示例中,多个线程可以同时访问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);
  }
}

4 个答案:

答案 0 :(得分:4)

该规则对于单身人士并不特殊。纯粹是:

  • 该方法是否需要支持多个调用它的线程,并且
  • 如果多个线程在错误的时间调用它,它会做任何会失败的事情

您的replaceNum的答案是“是的,它需要同步”(在方法级别上很有意义,因为其中的所有内容基本上都需要同步),因为它使用了ArrayList' s方法,它们不是线程安全的。为the Javadoc says

  

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

因此该方法需要同步对该ArrayList的访问。

给出这样的笼统声明,您必须假设所有方法都不是线程安全的,除非它明确声明。 (类似地,除非类明确声明 是线程安全的,否则您必须假设它不是。)

答案 1 :(得分:1)

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

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

仅当列表不为空时,才将其添加到列表中。因此,您永远不会在其中添加第一个元素。

几乎可以肯定这是一个逻辑缺陷,但是它可以书面回答这个问题。

假设您已解决逻辑问题,可以将元素添加到列表中,并且希望从多个线程中调用它,是的,应该对其进行同步。

但这不是 的原因,ArrayList被明确记录为从多个线程访问时需要同步(尽管表明同步必须以某种方式进行):

  

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

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

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

或者,如果两个线程都发现列表为空,则添加操作可能会被弄乱,因此不要尝试删除任何内容;然后都添加,最后在列表上有两个元素。

即使您按照Collections.synchronizedList文档中的建议使用ArrayList,也无法解决您的问题。重要的是要意识到,上面的引用是保存ArrayList 的不变量所要做的;它对强制您的代码的不变式没有任何作用。

您需要能够原子地执行该逻辑。整个过程就像一个“单一动作”一样,由具有对列表的独占访问权的一个线程完成。这是通过同步来完成的。

答案 2 :(得分:0)

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

答案 3 :(得分:0)

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

类似的东西:

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

另一个选择是您可以改用Vector,这使您的实现成为线程安全的。但是,它有其自身的性能问题。通常,与非线程安全的实现相比,线程安全的实现具有较慢的性能。