[英]Is it a thread-safe mechanism?
這個類是線程安全的嗎?
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();
}
}
你怎么看?
是的,只要你讓地圖最終完成。 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();
}
}
}
編輯
是的,如果你讓地圖final
。 否則,當第一次調用add()
時,並不能保證所有線程都能看到最新版本的地圖數據結構。
幾個線程可以到達if()
的主體。 putIfAbsent()
將確保只將一個AtomicLong
放入地圖中。
如果新值putIfAbsent()
, 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();
}
這就是為什么我更喜歡谷歌的CacheBuilder ,它有一個無法找到密鑰時調用的方法。 這樣,地圖只搜索一次,我不必創建額外的實例。
似乎沒有人有完整的解決方案,這是:
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();
}
那這個呢:
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最終字段語義(Java語言規范) ) if
是多余的,我希望我不會監督任何事情。 編輯:添加了Java語言規范的引用:
我想你最好用這樣的東西:
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返回的值)。
我假設你永遠不會重新創建地圖。
這個解決方案(請注意,我只顯示add
方法的主體 - 其余部分保持不變!)使您無法get
任何調用:
final AtomicLong newVal = new AtomicLong(),
prevVal = map.putIfAbsent(name, newVal);
return (prevVal != null? prevVal : newVal).incrementAndGet();
很可能額外get
比額外的new AtomicLong()
更昂貴。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.