使用时间戳更新的线程安全设置?

时间:2017-04-08 20:14:20

标签: java multithreading

我有一个包含数千个电话号码的Set。当我的服务收到新帐户请求时,它会根据列表检查电话号码,以确保它不是已知的垃圾邮件编号。如果电话号码列表早于一周,则该方法从外部服务器获取列表的最新副本并将其读入内存。然后它更新“timestamp”变量以反映列表的上次更新时间。类似的东西:

public class SpamPhoneNumberManager() {

  private Set<String> spamPhoneNumbers;
  private long timestamp;

  public SpamPhoneNumberManager() {
    updateSpamPhoneNumbers();
  }

  public Set<String> getSpamPhoneNumbers() {
    if(timestamp - System.currentTimeMillis() > ONE_WEEK) {
      updateSpamPhoneNumbers();
    }
    return spamPhoneNumbers;
  }

  private void updateSpamPhoneNumbers() {
    Set<String> newSpamPhoneNumbers = new HashSet<>();
    //populate set from file on server
    spamPhoneNumbers = Collections.unmodifiableSet(newSpamPhoneNumbers);
    timestamp = System.currentTimeMillis();
  }

}

多个线程可以同时调用get()方法。在当前的实现中,我想不出任何并发问题。在最糟糕的情况下,我可以提出,列表由多个线程连续更新。是否需要使这个线程安全?如果是这样,最好的方法是什么?

3 个答案:

答案 0 :(得分:1)

  

是否需要使这个线程安全?

您当前的类不是线程安全的,因为多个线程可以调用getSpamPhoneNumbers()并检查if条件,这不是原子的。 因此,多个线程尝试调用updateSpamPhoneNumbers导致竞争条件,因此将存在中间状态,其中spamPhoneNumbers获得一个值&amp; timestamp具有不同的值(如果任何其他线程调用get方法找到并返回下面解释的这些不一致的值。)

简而言之,会出现如下情况:

线程1 - &gt;使用spamPhoneNumbers更新spamPhoneNumbersThread1并设置timestampThread1

Thread2 - &gt;更新spamPhoneNumbersThread2(假设仍然timestamp未更新)

Thread3 - &gt;调用getSpamPhoneNumbers()并且不输入if块并返回spamPhoneNumbersThread2(针对timestampThread1验证)

重要的一点是,显然存在竞争条件,您将看到不一致(来自不同线程)timestampspamPhoneNumbers值。

  

如果是这样,最好的方法是什么?

解决方案是您需要在spamPhoneNumbers对象上进行同步,以便一旦线程一次只能访问它。

public Set<String> getSpamPhoneNumbers() {
    synchronized(spamPhoneNumbers) {
      if(timestamp - System.currentTimeMillis() > ONE_WEEK) {
        updateSpamPhoneNumbers();
      }
    }
    return spamPhoneNumbers;
  }

  private void updateSpamPhoneNumbers() {
    Set<String> newSpamPhoneNumbers = new HashSet<>();
      //populate set from file on server
      spamPhoneNumbers = Collections.unmodifiableSet(newSpamPhoneNumbers);
      timestamp = System.currentTimeMillis();
  }

PS:您在updateSpamPhoneNumbers()内不需要任何同步,因为它是private,但如果您将来改变主意,此方法将变为{{1} },你也必须在这里同步。

答案 1 :(得分:0)

如果您愿意只在电话号码集中添加一些项目,请使用任何线程安全的收集类型声明您的收藏,例如: Synchronized Set就足够了。

在此处阅读更多内容:Using Synchronized with Thread-Safe Collection?

答案 2 :(得分:0)

我会改变你的实现。使用Timer作为后台线程,使用固定费率一周更新目录或使用ScheduledExecutorService

以下是使用Timer的示例。

S