简体   繁体   English

它是一种线程安全的机制吗?

[英]Is it a thread-safe mechanism?

Is this class thread-safe? 这个类是线程安全的吗?

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

What do you think? 你怎么看?

Yes, provided you make the map final. 是的,只要你让地图最终完成。 The if is not necessary but you can keep it for performance reasons if you want, although it will most likely not make a noticeable difference: if是没有必要的,但如果你愿意,你可以出于性能原因保留它,尽管它很可能没有明显的区别:

public long add(String name) {
  this.map.putIfAbsent(name, new AtomicLong());
  return this.map.get(name).incrementAndGet();
}

EDIT 编辑

For the sake of it, I have quickly tested both implementation (with and without the check). 为此,我已经快速测试了两种实现(有和没有检查)。 10 millions calls on the same string take: 对同一个字符串进行10百万次调用:

  • 250 ms with the check 检查250毫秒
  • 480 ms without the check 480毫秒没有检查

Which confirms what I said: unless you call this method millions of time or it is in performance critical part of your code, it does not make a difference. 这证实了我所说的内容:除非你将这种方法称为数百万次,或者它是代码性能的关键部分,否则它没有什么区别。

EDIT 2 编辑2

Full test result - see the BetterCounter which yields even better results . 完整的测试结果 - 请参阅BetterCounter ,它可以产生更好的结果 Now the test is very specific (no contention + the get always works) and does not necessarily correspond to your usage. 现在测试非常具体(没有争用+ get始终有效)并且不一定与您的用法相对应。

Counter: 482 ms 计数器:482毫秒
LazyCounter: 207 ms LazyCounter:207毫秒
MPCounter: 303 ms MPCounter:303毫秒
BetterCounter: 135 ms 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();
        }
    }
}

EDIT 编辑

Yes if you make the map final . 是的,如果你让地图final Otherwise, it's not guaranteed that all threads see the most recent version of the map data structure when they call add() for the first time. 否则,当第一次调用add()时,并不能保证所有线程都能看到最新版本的地图数据结构。

Several threads can reach the body of the if() . 几个线程可以到达if()的主体。 The putIfAbsent() will make sure that only a single AtomicLong is put into the map. putIfAbsent()将确保只将一个AtomicLong放入地图中。

There should be no way that putIfAbsent() can return without the new value being in the map. 如果新值putIfAbsent()putIfAbsent()就不能返回。

So when the second get() is executed, it will never get a null value and since only a single AtomicLong can have been added to the map, all threads will get the same instance. 因此,当执行第二个get() ,它将永远不会获得null值,并且由于只能将一个AtomicLong添加到地图中,因此所有线程都将获得相同的实例。

[EDIT2] The next question: How efficient is this? [EDIT2]接下来的问题:这有多高效?

This code is faster since it avoids unnecessary searches: 此代码更快,因为它避免了不必要的搜索:

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

This is why I prefer Google's CacheBuilder which has a method that is called when a key can't be found. 这就是为什么我更喜欢谷歌的CacheBuilder ,它有一个无法找到密钥时调用的方法。 That way, the map is searched only once and I don't have to create extra instances. 这样,地图只搜索一次我不必创建额外的实例。

No one seems to have the complete solution, which is: 似乎没有人有完整的解决方案,这是:

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

What about this: 那这个呢:

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

Edit: Added a quote from the Java Language Specification: 编辑:添加了Java语言规范的引用:

I think you would be better off with something like this: 我想你最好用这样的东西:

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

Otherwise multiple threads may add simultaneously and you wouldn't notice (since you ignore the value returned by putIfAbsent). 否则多个线程可能会同时添加,您不会注意到(因为您忽略了putIfAbsent返回的值)。

I assume that you never recreate the map. 我假设你永远不会重新创建地图。

This solution (note that I am showing only the body of the add method -- the rest stays the same!) spares you of any calls to get : 这个解决方案(请注意,我只显示add方法的主体 - 其余部分保持不变!)使您无法get任何调用:

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

In all probability an extra get is much costlier than an extra new AtomicLong() . 很可能额外get比额外的new AtomicLong()更昂贵。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM