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

Java 使用时间戳更新的线程安全集?,java,multithreading,Java,Multithreading,我有一套可以装上千个电话号码。当我的服务收到新的帐户请求时,它会根据列表检查电话号码,以确保它不是已知的垃圾邮件号码。如果电话号码列表超过一周,该方法将从外部服务器获取列表的最新副本并将其读入内存。然后更新“timestamp”变量以反映列表的最后更新时间。比如: public class SpamPhoneNumberManager() { private Set<String> spamPhoneNumbers; private long timestamp; p

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

}
公共类spamphoneumbermanager(){
私人设置垃圾邮件发送者;
私有长时间戳;
公共SpamPhoneNumberManager(){
UpdatesPanphonenumbers();
}
公共集getSpamPhoneNumbers(){
if(timestamp-System.currentTimeMillis()>一周){
UpdatesPanphonenumbers();
}
返回spamphonenumber;
}
私有void updateSpamPhoneNumbers(){
Set newspaphonenumbers=new HashSet();
//从服务器上的文件填充集合
spamPhoneNumbers=Collections.unmodifiableSet(newSpamPhoneNumbers);
timestamp=System.currentTimeMillis();
}
}
多个线程可以同时调用get()方法。在当前的实现中,我想不出任何并发问题。在我能想到的最糟糕的情况下,列表由多个线程连续更新。是否需要使此线程安全?如果是这样,最好的方法是什么

是否需要使此线程安全

当前类不是线程安全的,因为多个线程可以调用
getSpamPhoneNumbers()
并检查
if
条件,该条件不是原子的。
因此,多个线程试图调用导致争用条件的
updatesphanmonenumbers
,因此会出现中间状态,在
spamPhoneNumbers
中,使用不同的值获取一个值&
timestamp
(如果任何其他线程调用
get
方法发现并返回这些不一致的值,解释如下)

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

Thread1->使用
spamphonenumberstream1
更新
spamPhoneNumbers
并设置
timestampThread1

Thread2->updates
spamPhoneNumbersThread2
(假设仍
timestamp
未更新)

Thread3->调用
getSpamPhoneNumbers()
,不输入if块并返回
spamphonenumberthread2
(根据
timestampThread1
验证)

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

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

解决方案是您需要在
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();
  }
public Set getSpamPhoneNumbers(){
已同步(SPAMPhonEnumber){
if(timestamp-System.currentTimeMillis()>一周){
UpdatesPanphonenumbers();
}
}
返回spamphonenumber;
}
私有void updateSpamPhoneNumbers(){
Set newspaphonenumbers=new HashSet();
//从服务器上的文件填充集合
spamPhoneNumbers=Collections.unmodifiableSet(newSpamPhoneNumbers);
timestamp=System.currentTimeMillis();
}

p.S.:你不需要在
updatesphonenumbers()
内部进行任何同步,因为它是
private
,但是如果你将来改变主意,并且此方法成为
公共的
,你也必须在这里进行同步。

如果你只想在你的电话号码集合中添加一些项目,使用任何线程安全的集合类型(例如)声明集合就足够了


请在此处阅读更多信息:

我将更改您的实现。以固定速率使用一周作为后台线程来更新目录,或使用

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

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;
    }

}
公共类SpamPhoneNumberManager{
私人设置垃圾邮件发送者;
私人最终定时器;
私有易失性布尔值IsUpdate=false;
公共SpamPhoneNumberManager(){
this.timer=new timer(“updater”,true);
this.timer.scheduleAtFixedRate(new TimerTask()){
@凌驾
公开募捐{
SpamPhoneNumberManager.this.UpdatesPanphonenumbers();
}
},011000*60*60*24*7);//一周//一周
}
公共集getSpamPhoneNumbers(){
如果(正在更新){
//这是您的决定,您可以选择执行什么操作,或者等待阻塞直到更新,或者返回旧副本,或者稍后重试异常
}
返回此.spamPhoneNumbers;
}
私有void updateSpamPhoneNumbers(){
this.isupdateing=true;
Set newspaphonenumbers=new HashSet();
//从服务器上的文件填充集合
this.spamPhoneNumbers=Collections.unmodifiableSet(newSpamPhoneNumbers);
this.isupdateing=false;
}
}

updatesphanphonenumbers
在更新时可以调用n次。你应该用锁。。为什么要使用静态变量?此外,您还可以返回列表的旧视图您的代码不太正确,上下文中的变量是
静态的
,但不是方法我经常看到这种代码,任何不幸触发“更新”的人都必须等待它完成,这会导致该调用的大量延迟。如果延迟不是一个问题,那么这是很好的,如果是,考虑使用一个在后台进行刷新的自填充缓存。更新的变量不是静态的@nachokk,我需要锁定get()函数中的逻辑吗?它怎么能返回一个旧的列表?在更新列表写入内存之前,时间戳不会更新,如果没有更新集,线程将永远看不到更新的时间戳,对吗?@BartW一个简单的修复方法是