Reputation: 35557
Let's say customer has a Credit Card
. And he has balance of x
amount of money and he is buying y
valued item(y<x)
. And again he is going to buy another item witch will cost z
.
(y+z>x but z<x
) .
Now I am going to simulate this scenario in Java
. If all transaction happens in sequential, there is no need to panic. Customer can buy y
valued item and then he don't have enough credit to buy other one.
But when we come in to multi-threaded environment we have to deal with some locking mechanism or some strategy. Because if some other thread read credit card object before reflect changes by previous thread serious issues will rise.
As far as I can see one way is we can keep a copy of original balance and we can check current value just before update the balance. If value is same as original one then we can make sure other threads doesn't change the balance. If balance different then we have to undo our calculation.
And again Java Synchronization also a good solution. Now my question is what will be the best approach to implement in such a scenario?
Additionally if we are going to see this in big picture. Synchronization hits the performance of the system. Since it is locked the object and other thread has to wait.
Upvotes: 4
Views: 859
Reputation: 269687
What you are talking about sounds like software transactional memory. You optimistically assume that no other threads will modify the data upon which your transaction depends, but you have a mechanism to detect if they have.
The types in the java.util.concurrent.atomic
package can help build a lock-free solution. They implement efficient compare-and-swap operations. For example, an AtomicInteger
reference would allow you to to something like this:
AtomicInteger balance = new AtomicInteger();
…
void update(int change) throws InsufficientFundsException {
int original, updated;
do {
original = balance.get();
updated = original + change;
if (updated < 0)
throw new InsufficientFundsException();
} while (!balance.compareAndSet(original, update));
}
As you can see, such an approach is subject to a livelocked thread condition, where other threads continually change the balance, causing one thread to loop forever. In practice, specifics of your application determine how likely a livelock is.
Obviously, this approach is complex and loaded with pitfalls. If you aren't a concurrency expert, it's safer to use a lock to provide atomicity. Locking usually performs well enough if code inside the synchronized
block doesn't perform any blocking operations, like I/O. If code in critical sections has a definite execution time, you are probably better off using a lock.
Upvotes: 2
Reputation: 5973
As far as I can see one way is we can keep a copy of original balance and we can check current value just before update the balance. If value is same as original one then we can make sure other threads doesn't change the balance. If balance different then we have to undo our calculation.
Sounds like what AtomicInteger.compareAndSet()
and AtomicLong.compareAndSet()
do.
An easier-to-understand approach would involve using synchronized
methods on your CreditCard
class that your code would call to update the balance. (Only one synchronized
method on an object can execute at any one time.)
In this case, it sounds like you want a public synchronized boolean makePurchase(int cost)
method that returns true
on success and false
on failure. The goal is that no transaction on your object should require more than one method call - as you've realized, you won't want to make two method calls on CreditCard
(getBalance()
and later setBalance()
) to do the transaction, because of potential race conditions.
Upvotes: 0
Reputation: 21616
I will prefer to have a ReadWriteLock
, this helps to to lock it for reading and writing, this is nice because you can have separate read and write lock for each resource:
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.readLock().lock();
// multiple readers can enter this section
// if not locked for writing, and not writers waiting
// to lock for writing.
readWriteLock.readLock().unlock();
readWriteLock.writeLock().lock();
// only one writer can enter this section,
// and only if no threads are currently reading.
readWriteLock.writeLock().unlock();
ReadWriteLock
internally keeps two Lock instances. One guarding read access, and one guarding write access.
Upvotes: 3
Reputation: 548
Your proposal doesn't fit. You can't be sure the context switch doesn't happen between check and update.
The only way is synchronization.
Upvotes: 2