它是线程安全机制吗?



这个类是线程安全的吗?

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();
}

编辑

为了它,我快速测试了两种实现(有检查和不检查(。 同一字符串上的 1000 万个调用需要:

  • 250 毫秒(带检查(
  • 480 毫秒(无检查(

这证实了我所说的:除非你调用这种方法数百万次,或者它是代码的性能关键部分,否则它不会有什么区别。

编辑 2

完整的测试结果 - 请参阅产生更好结果的BetterCounter。现在测试非常具体(没有争用 + get 总是有效(,不一定与您的使用情况相对应。

计数器: 482 ms
懒惰计数器:207 ms
MPCounter: 303 ms
更好计数器:135 ms

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() 就无法返回。

因此,当执行第二个get()时,它永远不会获得null值,并且由于只能将单个AtomicLong添加到映射中,因此所有线程都将获得相同的实例。

[编辑2] 下一个问题:效率如何?

此代码更快,因为它避免了不必要的搜索:

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,它有一个在找不到键时调用的方法。这样,地图只需搜索一次我就不必创建额外的实例。

似乎没有人有完整的解决方案,即:

  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();
  }
}
  • final map,以确保在调用第一个方法之前,它对所有线程完全可见。(有关详细信息,请参阅 17.5 最终字段语义(Java 语言规范(
  • 我认为if是多余的,我希望我没有监督任何事情。

编辑:添加了Java语言规范中的引用:

这个解决方案(请注意,我只显示add方法的主体 - 其余的保持不变!(免除了对get的任何调用:

final AtomicLong newVal = new AtomicLong(), 
                 prevVal = map.putIfAbsent(name, newVal);
return (prevVal != null? prevVal : newVal).incrementAndGet();

额外的get很可能比额外的new AtomicLong()贵得多。

我认为你最好这样:

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 返回的值(。

我假设您永远不会重新创建地图。

最新更新