Java 使用时间戳更新的线程安全集?
我有一套可以装上千个电话号码。当我的服务收到新的帐户请求时,它会根据列表检查电话号码,以确保它不是已知的垃圾邮件号码。如果电话号码列表超过一周,该方法将从外部服务器获取列表的最新副本并将其读入内存。然后更新“timestamp”变量以反映列表的最后更新时间。比如:Java 使用时间戳更新的线程安全集?,java,multithreading,Java,Multithreading,我有一套可以装上千个电话号码。当我的服务收到新的帐户请求时,它会根据列表检查电话号码,以确保它不是已知的垃圾邮件号码。如果电话号码列表超过一周,该方法将从外部服务器获取列表的最新副本并将其读入内存。然后更新“timestamp”变量以反映列表的最后更新时间。比如: public class SpamPhoneNumberManager() { private Set<String> spamPhoneNumbers; private long timestamp; p
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
中,使用不同的值获取一个值×tamp
(如果任何其他线程调用get
方法发现并返回这些不一致的值,解释如下)
简言之,将出现如下情况:
Thread1->使用spamphonenumberstream1
更新spamPhoneNumbers
并设置timestampThread1
Thread2->updatesspamPhoneNumbersThread2
(假设仍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一个简单的修复方法是