有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

使用时间戳更新的java线程安全集?

我有一套可以装上千个电话号码。当我的服务收到新的帐户请求时,它会根据列表检查电话号码,以确保它不是已知的垃圾邮件号码。如果电话号码列表超过一周,该方法将从外部服务器获取列表的最新副本并将其读入内存。然后更新“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) 个答案

  1. # 2 楼答案

    我会改变你的执行方式。使用Timer作为后台线程,以一周的固定速率更新目录,或使用ScheduledExecutorService

    下面是一个使用计时器的示例

    public class SpamPhoneNumberManager {
    
        private Set<String> spamPhoneNumbers;
        private final Timer timer;
        private volatile boolean isUpdating = false;
    
    
        public SpamPhoneNumberManager() {
            this.timer = new Timer("updater", true);
            this.timer.scheduleAtFixedRate(new TimerTask() {
                @Override
                public void run() {
                    SpamPhoneNumberManager.this.updateSpamPhoneNumbers();
                }
            }, 0, 1000 * 60 * 60 * 24 * 7);// one week // week
    
        }
    
        public Set<String> getSpamPhoneNumbers() {
            if(isUpdating){
                // here is your decision what to do, or wait blocking until is updated, or return an old copy, or exception to retry later 
            }
            return this.spamPhoneNumbers;
        }
    
        private void updateSpamPhoneNumbers() {
            this.isUpdating = true;
            Set<String> newSpamPhoneNumbers = new HashSet<>();
            // populate set from file on server
            this.spamPhoneNumbers = Collections.unmodifiableSet(newSpamPhoneNumbers);
            this.isUpdating = false;
        }
    
    }
    
  2. # 3 楼答案

    Is there a need to make this threadsafe?

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

    简言之,将出现如下情况:

    螺纹1->;用spamPhoneNumbersThread1更新spamPhoneNumbers并设置timestampThread1

    螺纹2->;更新spamPhoneNumbersThread2(假设仍然timestamp未更新)

    螺纹3->;调用getSpamPhoneNumbers(),不输入if块并返回spamPhoneNumbersThread2(根据timestampThread1验证)

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

    If so, what's the best way to do this?

    解决方案是需要对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();
      }
    

    p.S.:您不需要在updateSpamPhoneNumbers()内进行任何同步,因为它是private,但是如果您将来改变主意,并且此方法变为public,您也必须在此处进行同步