使用并发哈希映射时锁的必要性
Necessity of the locks while working with concurrent hash map
这是我的 classes 之一中的代码:
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();
}
}
}
您可以假定此 class 将在并发环境中使用。
问题是:您认为锁具的必要性如何?这段代码闻起来怎么样? :)
因为当您将它用作放入此地图的键时,您总是递增 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();
}
}
再看看public void put(String some)
里面的操作。
map.put(counter++, tmp);
sum.getAndAdd(tmp);
现在让我们看看各个部分。
counter
是一个可变变量。所以它只提供内存可见性而不提供原子性。由于counter++
是复合操作,所以需要加锁实现原子性
map.put(key, value)
是原子的,因为它是 ConcurrentHashMap
.
sum.getAndAdd(tmp)
是原子的,因为它是 AtomicInteger
.
如您所见,除了 counter++
之外,其他所有操作都是原子操作。但是,您正试图通过组合所有这些操作来实现某些功能。要在功能级别实现原子性,您需要一个锁。当线程在各个原子操作之间交错时,这将帮助您避免意外的副作用。
所以你需要一个锁,因为 counter++
不是原子的,你想结合一些原子操作来实现一些功能(假设你希望它是原子的).
这是我的 classes 之一中的代码:
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();
}
}
}
您可以假定此 class 将在并发环境中使用。
问题是:您认为锁具的必要性如何?这段代码闻起来怎么样? :)
因为当您将它用作放入此地图的键时,您总是递增 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();
}
}
再看看public void put(String some)
里面的操作。
map.put(counter++, tmp);
sum.getAndAdd(tmp);
现在让我们看看各个部分。
counter
是一个可变变量。所以它只提供内存可见性而不提供原子性。由于counter++
是复合操作,所以需要加锁实现原子性map.put(key, value)
是原子的,因为它是ConcurrentHashMap
.sum.getAndAdd(tmp)
是原子的,因为它是AtomicInteger
.
如您所见,除了 counter++
之外,其他所有操作都是原子操作。但是,您正试图通过组合所有这些操作来实现某些功能。要在功能级别实现原子性,您需要一个锁。当线程在各个原子操作之间交错时,这将帮助您避免意外的副作用。
所以你需要一个锁,因为 counter++
不是原子的,你想结合一些原子操作来实现一些功能(假设你希望它是原子的).