使用并发哈希映射时需要锁定

时间:2019-03-27 10:58:41

标签: java concurrency locking

这是我的一个课程中的代码:

 class SomeClass {

   private Map<Integer, Integer> map = new ConcurrentHashMap<>();
   private volatile int counter = 0;
   final AtomicInteger sum = new AtomicInteger(0); // will be used in other classes/threads too
   private ReentrantLock l = new ReentrantLock();

   public void put(String some) {
    l.lock();
    try {
        int tmp = Integer.parseInt(some);
        map.put(counter++, tmp);
        sum.getAndAdd(tmp);
    } finally {
        l.unlock();
    }
   }

   public Double get() {
    l.lock();
    try {
        //... perform some map resizing operation ...
        // some calculations including sum field ...
    } finally {
        l.unlock();
    }
   }

}

您可以假定此类将在并发环境中使用。

问题是:您如何看待锁的必要性?这段代码闻起来如何? :)

2 个答案:

答案 0 :(得分:1)

让我们看看public void put(String some)内部的操作。

  1. map.put(counter++, tmp);
  2. sum.getAndAdd(tmp);

现在让我们看一下各个部分。

  1. counter是易失性变量。因此,它仅提供内存可见性,而不提供原子性。由于counter++是复合操作,因此需要一个锁才能实现原子性。

  2. map.put(key, value)是原子的,因为它是ConcurrentHashMap

  3. sum.getAndAdd(tmp)是原子的,因为它是AtomicInteger

如您所见,除counter++外,其他所有操作都是原子操作。但是,您正在尝试通过组合所有这些操作来实现某些功能。要在功能级别上实现原子性,您需要一个锁。当线程在单个原子操作之间交织时,这将帮助您避免意外的副作用。

所以您需要一个锁,因为counter++不是原子的,并且您想结合一些原子操作来实现某些功能(假设,您希望这是原子的)。

答案 1 :(得分:0)

由于在将counter用作插入此地图的键时始终会使其递增:

map.put(counter++, tmp);

当您再次阅读它时:

return sum / map.get(counter);

map.get(counter)将是null,因此这将导致NPE(除非您在地图中放了2 ^ 32个以上的东西,ofc)。 (我假设您的意思是sum.get(),否则它将无法编译)。

因此,您可以具有等效功能而无需任何锁定:

class SomeClass {
  public void put(String some) { /* do nothing */ }
  public Double get() {
    throw new NullPointerException();
  }
}

您尚未真正解决edit的问题。 divisor仍然为空,因此不带锁的等效功能为:

class SomeClass {
  private final AtomicInteger sum = new AtomicInteger(0);

  public void put(String some) {
    sum.getAndAdd(Integer.parseInt(some));
  }

  public Double get() {
    return sum.get();
  }
}