Thread Safety:
I touched on a few things in some of the other points, but I haven't addressed the main implementation as a whole. The current implementation is not thread safe. While you are using a ConcurrentHashMap
to store the quantities, pickProduct()
and restockProduct()
both make multiple calls to that instance. ConcurrentHashMap
ensures that one thread can't call put()
while another thread calls get()
. However, it does not prevent the following:
- T1: map.contains(x);
- T2: map.remove(x);
- T1: map.get(x);
In this case, the class that has the reference to the Map
must ensure that multiple sequential calls happen in a logical block.
public Inventory {
private final Object _lock = new Object();
private final Map<Product, Integer> _quantities = new HashMap<>();
private final Map<Integer, Product> _idToProduct = new HashMap<>();
public pickProduct(int id, int quantity) {
synchronize(_lock) {
if (!idToProduct.containsKey(id)) {
throw new NoProductException(id); // the UI class would catch this and decide how to tell the user something bad happened
} else if (quantity <= 0)
throw new IllegalArgumentException(quantity);
}
Product product = _idToProduct.get(id);
int currentCount = _quantities.get(product); // Java will unbox the value for you
if (currentCount < quantity) {
throw InsufficentQuantityException(id, quantity);
}
_quantities.put(product, currentCount - quantity);
}
}
}
Once you follow the similar pattern for wrapping your public methods in synchronize
blocks, this will ensure that one thread can not partially execute pickProduct()
while another thread is trying to restockProduct()
the same product.