这个类是否是线程安全的?
class Counter {
private ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
if (this.map.get(name) == null) {
this.map.putIfAbsent(name, new AtomicLong());
}
return this.map.get(name).incrementAndGet();
}
}
您怎么看?
答案 0 :(得分:6)
是的,只要你让地图最终完成。 if是没有必要的,但如果你愿意,你可以出于性能原因保留它,尽管它很可能没有明显的区别:
public long add(String name) {
this.map.putIfAbsent(name, new AtomicLong());
return this.map.get(name).incrementAndGet();
}
编辑
为此,我已经快速测试了两种实现方式(有和没有检查)。对同一个字符串进行10百万次调用:
这证实了我所说的内容:除非您将此方法称为数百万次,或者它是您的代码中性能关键部分,否则它没有任何区别。
编辑2
完整的测试结果 - 请参阅 BetterCounter
,以获得更好的结果。现在测试非常具体(没有争用+ get始终有效)并且不一定与您的使用相符。
计数器:482毫秒
LazyCounter:207毫秒
MPCounter:303毫秒
BetterCounter:135毫秒
public class Test {
public static void main(String args[]) throws IOException {
Counter count = new Counter();
LazyCounter lazyCount = new LazyCounter();
MPCounter mpCount = new MPCounter();
BetterCounter betterCount = new BetterCounter();
//WARM UP
for (int i = 0; i < 10_000_000; i++) {
count.add("abc");
lazyCount.add("abc");
mpCount.add("abc");
betterCount.add("abc");
}
//TEST
long start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
count.add("abc");
}
long end = System.nanoTime();
System.out.println((end - start) / 1000000);
start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
lazyCount.add("abc");
}
end = System.nanoTime();
System.out.println((end - start) / 1000000);
start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
mpCount.add("abc");
}
end = System.nanoTime();
System.out.println((end - start) / 1000000);
start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
betterCount.add("abc");
}
end = System.nanoTime();
System.out.println((end - start) / 1000000);
}
static class Counter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
this.map.putIfAbsent(name, new AtomicLong());
return this.map.get(name).incrementAndGet();
}
}
static class LazyCounter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
if (this.map.get(name) == null) {
this.map.putIfAbsent(name, new AtomicLong());
}
return this.map.get(name).incrementAndGet();
}
}
static class BetterCounter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
AtomicLong counter = this.map.get(name);
if (counter != null)
return counter.incrementAndGet();
AtomicLong newCounter = new AtomicLong();
counter = this.map.putIfAbsent(name, newCounter);
return (counter == null ? newCounter.incrementAndGet() : counter.incrementAndGet());
}
}
static class MPCounter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
final AtomicLong newVal = new AtomicLong(),
prevVal = map.putIfAbsent(name, newVal);
return (prevVal != null ? prevVal : newVal).incrementAndGet();
}
}
}
答案 1 :(得分:2)
修改强>
是的,如果您制作地图final
。否则,当第一次调用add()
时,并不能保证所有线程都能看到最新版本的地图数据结构。
多个线程可以到达if()
的主体。 putIfAbsent()
将确保只有一个AtomicLong
被放入地图中。
如果地图中没有新值,putIfAbsent()
就无法返回。
因此,当执行第二个get()
时,它将永远不会获得null
值,并且因为只有一个AtomicLong
可以添加到地图中,所有线程将获得相同的实例
[EDIT2] 下一个问题:这有多高效?
此代码更快,因为它避免了不必要的搜索:
public long add(String name) {
AtomicLong counter = map.get( name );
if( null == counter ) {
map.putIfAbsent( name, new AtomicLong() );
counter = map.get( name ); // Have to get again!!!
}
return counter.incrementAndGet();
}
这就是为什么我更喜欢Google的CacheBuilder,它有一个无法找到密钥时调用的方法。这样,地图只会被搜索一次和我不需要创建额外的实例。
答案 2 :(得分:1)
似乎没有人有完整的解决方案,即:
public long add(String name) {
AtomicLong counter = this.map.get(name);
if (counter == null) {
AtomicLong newCounter = new AtomicLong();
counter = this.map.putIfAbsent(name, newCounter);
if(counter == null) {
counter = newCounter;
}
}
return counter.incrementAndGet();
}
答案 3 :(得分:0)
这个怎么样:
class Counter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
this.map.putIfAbsent(name, new AtomicLong());
return this.map.get(name).incrementAndGet();
}
}
map
应为final
,以确保在调用第一个方法之前,所有线程都可以完全看到它。 (有关详细信息,请参阅17.5 final Field Semantics (Java Language Specification))if
是多余的,我希望我不会监督任何事情。编辑:添加了Java语言规范的引用:
答案 4 :(得分:0)
我认为用这样的东西会更好:
class Counter {
private ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
AtomicLong counter = this.map.get(name);
if (counter == null) {
AtomicLong newCounter = new AtomicLong();
counter = this.map.putIfAbsent(name, newCounter);
if (counter == null) {
// The new counter was added - use it
counter = newCounter;
}
}
return counter.incrementAndGet();
}
}
否则多个线程可能同时添加,您不会注意到(因为您忽略了putIfAbsent返回的值)。
我假设你永远不会重新创建地图。
答案 5 :(得分:0)
此解决方案(请注意,我只显示add
方法的正文 - 其余方法保持不变!)让您免除对get
的任何调用:
final AtomicLong newVal = new AtomicLong(),
prevVal = map.putIfAbsent(name, newVal);
return (prevVal != null? prevVal : newVal).incrementAndGet();
很可能额外的get
比额外new AtomicLong()
更昂贵。