Efficient implementation of exclusive execution
I 开发者_高级运维have an ObjectManager class that is used to process payments. It is wrapped over the Order entities, so new instance has to be created when processing is required. I need to prevent the situation when several ObjectManager instances are dealing with the same order simultaneously (it happend once because of some errors on the remote payment processing center, somehow they called our callback urls twice). I'd love to get an advice how to implement it more efficiently.
For now, I am thinking about something like that:
public class OrderManager{
private static final CopyOnWriteArrayList<Integer> LOCKER =
new CopyOnWriteArrayList<Integer>();
private static synchronized boolean tryLock(Integer key) {
return LOCKER.addIfAbsent(key);
}
private static void releaseLock(Integer key) {
LOCKER.remove(key);
}
public void processPayment(Integer orderId) throws Exception{
if (!tryLock(orderId)) {
return;
}
try {
//operate
} finally {
releaseLock(orderId);
}
}
//remainder omitted
}
Using CopyOnWriteArrayList is cheap for reads, but expensive for writes, since you appear to be doing mostly writes, I suggest a collection which is O(1) for writes.
e.g.
private static final Set<Integer> LOCKER = Collections.newSetFromMap(
new ConcurrentHashMap<Integer, Boolean>());
private static boolean tryLock(Integer key) {
return LOCKER.add(key);
}
private static void releaseLock(Integer key) {
LOCKER.remove(key);
}
I lately implemented a generic multi-lock-handler mechanism, that may be suitable for you.
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
/**
* A synchronization utility for improving lock granularity.
*
* The class is suitable when some operation is associated with an id, and we don't want different
* threads to perform the operation on the same id concurrently. Concurrent executions on different ids is ok.
*
* @param K - the type of the id
*
* @author eschneider
*/
public class MultiLock <K>{
private ConcurrentHashMap<K, ReentrantLock> locks = new ConcurrentHashMap<K, ReentrantLock>();
/**
* Locks on a given id.
* Make sure to call unlock() afterwards, otherwise serious bugs may occur.
* It is strongly recommended to use try{ }finally{} in order to guarantee this.
* Note that the lock is re-entrant.
* @param id The id to lock on
*/
public void lock(K id) {
while (true) {
ReentrantLock lock = getLockFor(id);
lock.lock();
if (locks.get(id) == lock)
return;
else // means that the lock has been removed from the map by another thread, so it is not safe to
// continue with the one we have, and we must retry.
// without this, another thread may create a new lock for the same id, and then work on it.
lock.unlock();
}
}
/**
* Tries locking on a given id, giving up if somebody else has the lock.
* Make sure to call unlock() later in case that the lock is acquired, otherwise serious bugs may occur.
* It is strongly recommended to use try{ }finally{} in order to guarantee this. Note that the lock is re-entrant.
* @param id The id to lock on
* @return true iff the lock has been acquired
*/
public boolean tryLock(K id) {
while (true) {
ReentrantLock lock = getLockFor(id);
if (!lock.tryLock())
return false;
if (locks.get(id) == lock)
return true;
else // means that the lock has been removed from the map by another thread, so it is not safe to
// continue with the one we have, and we must retry.
// without this, another thread may create a new lock for the same id, and then work on it.
lock.unlock();
}
}
/**
* Unlocks on a given id.
* If the lock is not currently held, an exception is thrown.
*
* @param id The id to unlock
* @throws IllegalMonitorStateException in case that the thread doesn't hold the lock
*/
public void unlock(K id) {
ReentrantLock lock = locks.get(id);
if (lock == null || !lock.isHeldByCurrentThread())
throw new IllegalMonitorStateException("Lock for " + id + " is not owned by the current thread!");
locks.remove(id);
lock.unlock();
}
/**
* Gets/creates a lock for a given id. If many threads try it on the same time,
* they will all get the same lock.
* @param id The id
* @return The lock corresponding to the given id
*/
private ReentrantLock getLockFor(K id) {
ReentrantLock lock = locks.get(id);
if (lock == null) {
lock = new ReentrantLock();
ReentrantLock prevLock = locks.putIfAbsent(id, lock);
if (prevLock != null)
lock = prevLock;
}
return lock;
}
}
I think I would implement the lock in the database level - so if the ObjectManager being run in multiple processes it would still recognize the lock.
精彩评论