这是我的一个课程中的代码:
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();
}
}
}
您可以假定此类将在并发环境中使用。
问题是:您如何看待锁的必要性?这段代码闻起来如何? :)
答案 0 :(得分:1)
让我们看看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++
不是原子的,并且您想结合一些原子操作来实现某些功能(假设,您希望这是原子的)。
答案 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();
}
}