How do I acquire lock on multiple items?
Consider the example below,
Map<String, Account> accountMap = new HashMap<>(); // key=accountNumber,value=AccountObject
class Account{
private String accountNumber; // getter & setter
private double accountBalance; // getter & setter
}
I need to transfer funds from one account to another, so I was thinking of having a nested synchronized block and realized that it would lead to deadlock.
// bad code
synchronized(accountMap.get(accountNumber1)){
synchronized(accountMap.get(accountNumber2)){
// business logic
}
}
Also, I don't want a single lock because it would block the processing of all the threads for one transaction. Something like below
//bad code
Object mutex = new Object();
synchronized(mutex){
// business logic with accountNumber1 & accountNumber2
}
How do I go about solving this issue? I need to maintain locks only for two account objects.
Also, possible duplicate(but I wanted to know if there are different solutions). Preventing deadlock in a mock banking database
It is not possible to obtain a single lock on multiple objects. But there are alternatives.
You can use a shared (aka "global") lock. The problem is that this lock could be a bottleneck.
You can use some other object as a proxy for the two objects, and obtain a lock on that. For example, assuming that your accounts have unique ids:
String lockName = (acc1.getAccountNumber() + "-"
+ acc2.getAccountNumber()).intern();
synchronized (lockName) {
// we now have a lock on the combination of acc1 and acc2.
}
(However, this probably doesn't work in your use-case, because it doesn't stop a simultaneous transfer involving one of the two accounts and a third one.)
Obtain the locks in a canonical order. For example.
if (acc1.getAccountNumber().compareTo(acc2.getAccountNumber()) < 0) {
synchronized (acc1) {
synchronized (acc2) {
// do transfer
}
} else {
synchronized (acc2) {
synchronized (acc1) {
// do transfer
}
}
}
If locks are obtained in the same order by all threads, deadlock is not possible.
Acquire the locks using Lock.tryLock with a times. However this has a couple of problems:
You now need to manage per-account Lock objects.
There is the (theoretical) problem of "livelock". To address this you can use randomly generated timeout values.
(Note: don't attempt to use the identity hashcode as a proxy for a unique id. Identity hashcodes are not unique, and you also have the problem that you could have multiple in-memory Account objects that represent the same logical account ... if they are DTOs.)
You can lock on two objects and still avoid the deadlock using lock ordering
if (accountNumber1 < accountNumber2) {
synchronized (accountMap.get(accountNumber1)) {
synchronized (accountMap.get(accountNumber2)) {
//
}
}
} else {
synchronized (accountMap.get(accountNumber2)) {
synchronized (accountMap.get(accountNumber1)) {
//
}
}
}
}
This way we avoid the circular wait.
I assume that your Hashmap writes are thread-safe or it is read-only.
Related
I have a scenario where i have to maintain a Map which can be populated by multiple threads ,each modifying there respective List (unique identifier/key being thread name) and when the list size for a thread exceeds a fixed batch size we have to persist the records in DB.
Sample code below:
private volatile ConcurrentHashMap<String, List<T>> instrumentMap = new ConcurrentHashMap<String, List<T>>();
private ReadWriteLock lock ;
public void addAll(List<T> entityList, String threadName) {
try {
lock.readLock().lock();
List<T> instrumentList = instrumentMap.get(threadName);
if(instrumentList == null) {
instrumentList = new ArrayList<T>(batchSize);
instrumentMap.put(threadName, instrumentList);
}
if(instrumentList.size() >= batchSize -1){
instrumentList.addAll(entityList);
recordSaver.persist(instrumentList);
instrumentList.clear();
} else {
instrumentList.addAll(entityList);
}
} finally {
lock.readLock().unlock();
}
}
There is one more separate thread running after every 2 minutes to persist all the records in Map (to make sure we have something persisted after every 2 minutes and map size does not gets too big) and when it starts it block all other threads (check the readLock and writeLock usawhere writeLock has higher priority)
if(//Some condition) {
Thread.sleep(//2 minutes);
aggregator.getLock().writeLock().lock();
List<T> instrumentList = instrumentMap .values().stream().flatMap(x->x.stream()).collect(Collectors.toList());
if(instrumentList.size() > 0) {
saver.persist(instrumentList);
instrumentMap .values().parallelStream().forEach(x -> x.clear());
aggregator.getLock().writeLock().unlock();
}
This solution is working fine almost for every scenario we tested except sometime we see some of the records went missing i.e. not persisted at all although they were added fine in Map
My question is what is the problem with this code?
Is ConcurrentHashMap not the best solution here?
Does usage of read/write lock has some problem here?
Should i go with sequential processing?
No, it's not thread safe.
The problem is that you are using the read lock of the ReadWriteLock. This doesn't guarantee exclusive access for making updates. You'd need to use the write lock for that.
But you don't really need to use a separate lock at all. You can simply use the ConcurrentHashMap.compute method:
instrumentMap.compute(threadName, (tn, instrumentList) -> {
if (instrumentList == null) {
instrumentList = new ArrayList<>();
}
if(instrumentList.size() >= batchSize -1) {
instrumentList.addAll(entityList);
recordSaver.persist(instrumentList);
instrumentList.clear();
} else {
instrumentList.addAll(entityList);
}
return instrumentList;
});
This allows you to update items in the list whilst also guaranteeing exclusive access to the list for a given key.
I suspect that you could split the compute call into computeIfAbsent (to add the list if one is not there) and then a computeIfPresent (to update/persist the list): the atomicity of these two operations is not necessary here. But there is no real point in splitting them up.
Additionally, instrumentMap almost certainly shouldn't be volatile. Unless you really want to reassign its value (given this code, I doubt that), remove volatile and make it final.
Similarly, non-final locks are questionable too. If you stick with using a lock, make that final too.
Normally I would lock on a critical section like the following.
public class Cache {
private Object lockObject = new Object();
public Object getFromCache(String key) {
synchronized(lockObject) {
if (cache.containsKey(key)) {
// key found in cache - return cache value
}
else {
// retrieve cache value from source, cache it and return
}
}
}
}
The idea being I avoid a race condition which could result in the data source being hit multiple times and the key being added to the cache multiple times.
Right now if two threads come in at about the same time for different cache keys, I will still block one.
Assuming the keys are unique - will the lock still work by locking on the key?
I think it won't work because I understand that the object reference should be the same for the lock to come into effect. I guess this comes down to how it checks for equality.
public class Cache {
public Object getFromCache(String key) {
synchronized(key) {
if (cache.containsKey(key)) {
// key found in cache - return cache value
}
else {
// retrieve cache value from source, cache it and return
}
}
}
}
public class Cache {
private static final Set<String> lockedKeys = new HashSet<>();
private void lock(String key) throws InterruptedException {
synchronized (lockedKeys) {
while (!lockedKeys.add(key)) {
lockedKeys.wait();
}
}
}
private void unlock(String key) {
synchronized (lockedKeys) {
lockedKeys.remove(key);
lockedKeys.notifyAll();
}
}
public Object getFromCache(String key) throws InterruptedException {
try {
lock(key);
if (cache.containsKey(key)) {
// key found in cache - return cache value
}
else {
// retrieve cache value from source, cache it and return
}
} finally {
unlock(key);
}
}
}
try-finally - is very important - you must guarantee to unlock waiting threads after your operation even if your operation threw exception.
It will not work if your back-end is distributed across multiple servers/JVMs.
Each object has an implicit monitor upon which synchronization works. String object may be created in heap and also may be different for same set of characters (if created by using new) or may be from pool. Two threads will acess the critical section with synchronized block only if they synchronize on same object.
Synchronizing on String literal is really a bad idea. String literal from pool are shared. Just imagine if at two different parts of your code you are having two synchronized sections and you synchronize on two references of String but initilized with string with same set of characters, if String from pool is used then both the places it will be the same object. Even though both the places may have different business context but still you may end up in your application being hanged. It will be very difficult to debug too.
For the specific to the question of will the purpose be solved if synchronization is done on keys.
You want to avoid two threads trying to write without reading the latest value of cache. You will have different key for each entry. Suppose a thread1 and thread2 wants to access the same key then synchronization on the same key object will prevent both of them to enter the synchronized block. Meanwhile if a thread3 wants to access another different key then it can very well do so. Here we see the read and writes are faster as compared to single common object for reads and writes for all keys. So far so good but the problem will arise if suppose you are keeping an array or any other similar non thread safe data structure for storing the cached values. Simultaneous writes (for two or more different keys) can result in one write being overwritten by another at same index.
So it depends upon the implementation of cache data structure how best you can prepare it for faster read and writes in a multi threaded enviornment.
Sorry for my English
I don't use any of fields for the locking because so I shouldn't think about could or couldn't some field have value null.
I always create special fields used only for locking in thread synchronization.
For example:
public class Worker {
private static final List<Toilet> TOILETS = Arrays.asList(
new Toilet(1),
new Toilet(2),
// ...
new Toilet(NUMBER_OF_FLOORS)
);
// here it is:
private static final List<String> LOCK_TOILETS = Arrays.asList(
"LOCK TOILET #1",
"LOCK TOILET #2",
// ...
"LOCK TOILET #" + NUMBER_OF_FLOORS
);
private final int floorNumber;
public void spendWorkingHours() {
for (int i = 0; i < X; ++i) {
doWork();
snackSomething();
String lockToilet = LOCK_TOILETS.get(floorNumber);
Toilet theOnlyToiletOnTheFloor = TOILETS.get(floorNumber);
synchronized (lockToilet) {
goToToilet(theOnlyToiletOnTheFloor);
}
}
}
}
You should not use Strings for lock objects especially not string literals.
String literals are from the String pool and each String literal which is the same string is the same reference. This means if 2 different threads use 2 "different" string literals are actually the same and hence deadlock can easily occur.
To demonstrate:
// Thread #1
String LOCK1 = "mylock";
synchronized (LOCK1) {
}
// Thread #2
String LOCK2 = "mylock";
synchronized (LOCK2) {
// This is actually the SAME lock,
// might cause deadlock between the 2 synchronized blocks!
// Because LOCK1==LOCK2!
}
Best would be to synchronize on private objects which are not accessible from the "outside". If you use an Object for lock which is visible from "outside" (or returned by a method), that object is available to anyone to also use as a lock which you have no control over and may cause a deadlock with your internal synchronized block.
For example you can synchronize on the object you whish to guard if it is private, or create a private, internal lock Object:
private final Object LOCK = new Object();
// Later:
synchronized (LOCK) {
// LOCK is not known to any "outsiders", safe to use it as internal lock
}
Using a String may not be the best idea, because this class gets a bit of special treatment, and strings with the same contents may be reused (so locking a toilet on the first floor would also lock the toilet with the same number on the other floors).
Your best choice here is locking the actual toilet.
There is no need for lockToilet why don't you just use a synchronized statement over each TOILET resource?
Toilet t;
syncrhonized(TOILETS)
{
t = = TOILETS.get(floorNumber);
}
synchronized (t) {
goToToilet(t);
}
syncrhonized In this code means that any use of the object between parentheses is thread exclussive within the scope between brakets thus this object becoming a lock.
Answers do cover your question regarding the use of Strings for locking (See String interning for more details) so I will just mention a few other considerations:
Although you have defined the List as final (Cannot assign another list instance) and initialized with .asList(..) (Cannot change size) this doesn't make read-only or thread-safe, so if someone changes elements in that list you might get into an unstable state. Consider using a read-only list.
You also need to clarify the scope of locking. What are you trying to lock against? If goToToilet changes the object attributes, then the point of synchronization would be better placed in the method that changes the state of the Object. (This is a design recommendation; The code would work but would also be prone to errors when changing the code in the future)
Finally, I would also have a look in java concurrent structures as you might find concurrent collections and locking mechanisms useful.
If I have a class like :
class MultiThreadEg {
private Member member;
public Integer aMethod() {
..............
..............
}
public String aThread() {
...............
member.memberMethod(.....);
Payment py = member.payment();
py.processPayment();
...........................
}
}
Suppose that aThread() is a new thread, then, will accessing the shared member object by too many threads at the same time cause any issues (with the following access rules)?
Rule 1 : ONLY reading, no writing to the object(member).
Rule 2 : For all the objects that need some manipulation(writing/modification), a copy of the original object will be created.
for eg: In the payment() method, I do this :
public class Member {
private Payment memPay;
public payment() {
Payment py = new Payment(this.memPay);//Class's Object copy constructor will be called.
return py;
}
}
My concern is that, even though I create object copies for "writing" (like in the method payment()), acessing the member object by too many threads at the same time will cause some discrepancies.
What is the fact ? Is this implementation reliable in every case (0 or more concurrent accesses) ? Please advise. Thanks.
You could simply use a ReentrantReadWriteLock. That way, you could have multiple threads reading at the same time, without issue, but only one would be allowed to modify data. And Java handles the concurrency for you.
ReadWriteLock rwl = new ReentrantReadWriteLock();
Lock readLock = rwl.readLock;
Lock writeLock = rwl.writeLock;
public void read() {
rwl.readLock.lock();
try {
// Read as much as you want.
} finally {
rwl.readlock.unlock();
}
}
public void writeSomething() {
rwl.writeLock.lock();
try {
// Modify anything you want
} finally {
rwl.writeLock.unlock();
}
}
Notice that you should lock() before the try block begins, to guarantee the lock has been obtained before even starting. And, putting the unlock() in the finally clause guarantees that, no matter what happens within the try (early return, an exception is thrown, etc), the lock will be released.
In case update to memPay depends on the memPay contents (like memPay.amount+=100) you should block access for other threads when you are updating. This looks like:
mutual exclusion block start
get copy
update copy
publish copy
mutual exclusion block end
Otherwise there could be lost updates when two threads simultaneously begin update memPay object.
Example scenario:
Create two SynchronizedSets (s1 and s2)
Pass them to two threads (T1 and T2)
Start the threads
T1's run() :
while (forever)
s1.equals(s2)
T2's run() :
while (forever)
s2.equals(s1)
What happens?
- SynchronizedSet's equals acquires lock on itself
It computes the length of the param that's passed in and also what it contains to determine whether that is equal [Note: this is a guess based on the logs I analyzed]
If the passed in param is also a SynchronizedSet, calls to size() and containAll() implies lock of that has to be acquired as well.
In the above example, lock acquiring orders for T1 and T2 are as follows:
T1: s1 -> s2
T2: s2 -> s1
Ofc, it leads to a deadlock.
This problem is not specific to Synchronized Collections alone. It can happen even with Hashtable or Vector.
I believe this is a Java API limitation (design). How to overcome this? How do I ensure this does not happen in my application? Is there some design principle I should follow without getting into this situation?
I believe this is a Java API limitation (design).
I believe that you are wrong. A fundamental requirement of every PL level locking scheme that I've ever used is that threads must lock resources in the same order or risk deadlock. This applies to databases as well.
In fact, the only way I think you could avoid this would be to either:
require the application to acquire all locks that it needed in a single atomic operation, or
do all locking using a single global lock.
Both of these approaches is impractical and unscalable.
How to overcome this?
Code your application so that locks are acquired by all threads in the same order. #Maurice's and #Nettogrof's answers give examples of how to do this, though it may be more difficult if you have lots of sets to worry about.
You can lock both sets in the same order in each thread:
synchronized(s1) {
synchronized(s2) {
s1.equals(s2);
}
}
and
synchronized(s1) {
synchronized(s2) {
s2.equals(s1);
}
}
I may suggest to use a synchronized(){] block.
something like this:
while(forever){
synchronized(s1){
s1.equals(s2);
}
}
and
while(forever){
synchronized(s1){
s2.equals(s1);
}
}
You could order the sets by their identiyHashCode() before doing the equals() call. This way the order of the lock acquisition will always be the same.
Stephen C's is good stuff. Further information: If you don't know which way around the sets are, you can use a "global" lock whenever both sets are going to be compared:
private static final Object lock = new Object(); // May be context-local.
[...]
synchronized (lock) {
synchronized (s1) {
synchronized (s2) {
return s1.equals(s2);
}
}
}
If the sets are likely to be contended, you can most of the time order by identity hash code and fall back to the global lock:
int h1 = System.identityHashCode(s1);
int h2 = System.identityHashCode(s2);
return
h1<h2 ? lockFirstEquals(h1, h2) :
h2<h1 ? lockFirstEquals(h2, h1) :
globalLockEquals(h1, h2);
Sometimes, you can use an alternative algorithm. IIRC, StringBuffer can deadlock with append (although the combination of operations on the objects doesn't make a great deal of sense). It could be implemented as:
public StringBuffer append(StringBuffer other) {
if (other == null) {
return append("null");
}
int thisHash = System.identityHashCode(this);
int otherHash = System.identityHashCode(other);
if (thisHash < otherHash) {
synchronized (this) {
synchronized (other) {
appendImpl(other);
}
}
} else if (otherHash < thisHash) {
synchronized (other) {
synchronized (this) {
appendImpl(other);
}
}
} else {
append(other.toString()); // Or append((Object)other);
}
return this;
}
The best solution would probably to change your threading strategy so you don't need any locking here.