我想知道如果一个方法只调用另一个线程安全的方法是否会是线程安全的。我有一个这样的例子。 docs指出,ConcurrentSkipListSet
是线程安全的。
public class MessageHolder {
private Set<String> processedIds;
public MessageHolder() {
this.processedIds = new ConcurrentSkipListSet<>();
}
public void add (String id) {
processedIds.add(id);
}
public boolean contains (String id) {
return processedIds.contains(id);
}
public void remove (String id) {
processedIds.remove(id);
}
}
您可能会问为什么我不直接使用ConcurrentSkipListSet
。原因是我想为此处执行的操作创建一个接口,此示例将类似于内存中的版本。
答案 0 :(得分:1)
我认为您应该在评论中进行说明,也应弄清导致竞赛状况的原因。
在竞争条件下-所有线程的基本思想是,在执行任何时间,您所处的当前线程都可以重新安排到以后的时间,或者另一个线程可能正在并行执行并访问相同的数据。
对于其中一个,进程ID应该如前所述是最终的。除非您这样做,否则您的代码将不会很安全。即使ConcurrentSkipListSet <>是线程安全的,也不会阻止变量processIds被另一个线程重新分配。另外,Java很奇怪,您的processIds字段必须标记为final,以确保在构造函数完成之前对其进行了初始化。我找到了这个stackoverflow帖子,该帖子解释了Java中对象构造的一些问题,以供更多阅读。 Constructor synchronization in Java。基本上,不要将构造函数字段标记为已同步,但是,如果要保证在构造函数中进行变量初始化(在这种情况下可以这样做),则将字段标记为final。
要回答关于此线程是否安全的问题,唯一的答案是,这取决于您期望的客户端使用情况。您提供的方法对于编写的预期目的确实是线程安全的,但是客户端可以使用它们并产生竞争条件。您关于是否需要使用synced关键字100%的直觉是正确的。但是,这些注释也暗示着,如果不使这些方法显式地成为线程安全的,那么将来对代码的可维护性和正确性可能会产生可怕的后果。
客户端仍然可以以不安全的方式使用您提供的API,这可能会导致竞争状况,如注释之一中所述。如果要为客户端提供接口,则可能对此不关心...,或者可能对此并不在意,并希望提供一种机制,使客户端可以在保证线程安全的情况下对类进行多次访问。
总体而言,出于以下两个原因,我可能建议您将方法标记为已同步:1)它使客户端清楚地知道它们正在访问线程安全的方法,这一点非常重要。我可以轻松地想象这样一种情况,即客户端决定在不需要锁的情况下决定使用锁,这会损害性能。 2)将来有人可以更改您的方法以包含一些不同的逻辑,这些逻辑要求使用synced关键字(这不太可能,因为您似乎已经处于线程化环境中)。