我有一个包含数千个电话号码的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()方法。在当前的实现中,我想不出任何并发问题。在最糟糕的情况下,我可以提出,列表由多个线程连续更新。是否需要使这个线程安全?如果是这样,最好的方法是什么?
答案 0 :(得分:1)
是否需要使这个线程安全?
您当前的类不是线程安全的,因为多个线程可以调用getSpamPhoneNumbers()
并检查if
条件,这不是原子的。
因此,多个线程尝试调用updateSpamPhoneNumbers
导致竞争条件,因此将存在中间状态,其中spamPhoneNumbers
获得一个值&amp; timestamp
具有不同的值(如果任何其他线程调用get
方法找到并返回下面解释的这些不一致的值。)
简而言之,会出现如下情况:
线程1 - &gt;使用spamPhoneNumbers
更新spamPhoneNumbersThread1
并设置timestampThread1
Thread2 - &gt;更新spamPhoneNumbersThread2
(假设仍然timestamp
未更新)
Thread3 - &gt;调用getSpamPhoneNumbers()
并且不输入if块并返回spamPhoneNumbersThread2
(针对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();
}
PS:您在updateSpamPhoneNumbers()
内不需要任何同步,因为它是private
,但如果您将来改变主意,此方法将变为{{1} },你也必须在这里同步。
答案 1 :(得分:0)
如果您愿意只在电话号码集中添加一些项目,请使用任何线程安全的收集类型声明您的收藏,例如: Synchronized Set就足够了。
答案 2 :(得分:0)