Putting the value in ConcurrentHashMap is atomic or not? - java

I am working on a project in which I am making connections to database. And I need to see how many times an exception is happening if there are any. I am working with Multithreaded code, meaning multiple threads will be making connection to database and inserting into database. So it might be possible that at some point connection get lost so we need to see how many times those exception has occurred.
So I wrote a below code and in the catch block, I am catching exception and making a counter to increased every time if there is any exeption and putting it in ConcurrentHashMap.
class Task implements Runnable {
public static final AtomicInteger counter_sql_exception = new AtomicInteger(0);
public static final AtomicInteger counter_exception = new AtomicInteger(0);
public static ConcurrentHashMap<String, Integer> exceptionMap = new ConcurrentHashMap<String, Integer>();
#Override
public void run() {
try {
//Make a db connection and then executing the SQL-
} catch (SQLException e) {
synchronized(this) {
exceptionMap.put(e.getCause().toString(), counter_sql_exception.incrementAndGet());
}
LOG.Error("Log Exception")
} catch (Exception e) {
synchronized(this) {
exceptionMap.put(e.getCause().toString(), counter_exception.incrementAndGet());
}
LOG.Error("Log Exception")
}
}
}
My Question is- Today I had a code review and one of my senior team members said, you won't be needing synchronized(this) on the exceptionMap in the catch block. I said yes we will be needing because incrementing the counter is atomic. Putting a new value in the map is atomic. But doing both without synchronization is not atomic . And he said ConurrentHashMap will be doing this for you.
So does I will be needing synchronized(this) block on that exceptionMap or not.? If not then why? And if Yes then what reason should I quote to him.

if you are tying to count the number of times each exception occured, then you need something like this:
private static final ConcurrentMap<String, AtomicInteger> exceptionMap = new ConcurrentHashMap<String, AtomicInteger>();
private static void addException(String cause) {
AtomicInteger count = exceptionMap.get(cause);
if(count == null) {
count = new AtomicInteger();
AtomicInteger curCount = exception.putIfAbsent(cause, count);
if(curCount != null) {
count = curCount;
}
}
count.incrementAndGet();
}
note that having a static map of exceptions is a resource leak unless you periodically clean it out.
As #dnault mentioned, you could also use guava's AtomicLongMap.
UPDATE: some comments on your original:
you are correct, that you do need another wrapping synchronized block to ensure that the latest value actually makes it into the map. however, as #Perception already pointed out in comments, you are synchronizing on the wrong object instance (since you are updating static maps, you need a static instance, such as Task.class)
however, you are using a static counter, but a String key which could be different for different exceptions, thus you aren't actually counting each exception cause, but instead sticking random numbers in as various map values
lastly, as i've shown in my example, you can solve the aforementioned issues and discard the synchronized blocks completely by making appropriate use of the ConcurrentMap.

And this way should also work.
private static final ConcurrentMap<String, Integer> exceptionMap = new ConcurrentHashMap<String, Integer>();
private static void addException(String cause) {
Integer oldVal, newVal;
do {
oldVal = exceptionMap .get(cause);
newVal = (oldVal == null) ? 1 : (oldVal + 1);
} while (!queryCounts.replace(q, oldVal, newVal));
}

ConcurrentHashMap doesn't allow null values, so replace will throw exception if called with oldValue == null. I used this code to increase counter by delta with returning oldValue.
private final ConcurrentMap<Integer,Integer> counters = new ConcurrentHashMap<Integer,Integer>();
private Integer counterAddDeltaAndGet(Integer key, Integer delta) {
Integer oldValue = counters.putIfAbsent(key, delta);
if(oldValue == null) return null;
while(!counters.replace(key, oldValue, oldValue + delta)) {
oldValue = counters.get(key);
}
return oldValue;
}

You don't have to use synchronized block and AtomicInteger. You can do it just with ConcurrentHashMap.compute method which is a thread-safe atomic operation. So your code will look something like this
public class Task implements Runnable {
public static final Map<String, Integer> EXCEPTION_MAP = new ConcurrentHashMap<String, Integer>();
#Override
public void run() {
try {
// Make a db connection and then executing the SQL-
} catch (Exception e) {
EXCEPTION_MAP.compute(e.getCause().toString(), (key, value) -> {
if (value == null) {
return 1;
}
return ++value;
});
}
}
}

Related

Thread-safe locking/sychronizing to one permit per resource with multiple resources

I need to implement thread-safe synchronization to multiple resources, where each resource can be accessed by one thread at a time, but different resources can be accessed concurrently. I have come up with the following code, meant to be used in a try-with-resources statement.
public class Gatekeeper implements AutoCloseable
{
private static final ConcurrentMap<Long, ReentrantLock> lockMap = new ConcurrentHashMap<>();
private final ReentrantLock lock;
private final Long key;
public Gatekeeper(Long key)
{
this.key = key;
lock = lockMap.computeIfAbsent(key, (Long absentKey) -> new ReentrantLock(true)); // computeIfAbsent is an atomic operation
try
{
lock.tryLock(30, TimeUnit.SECONDS);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw new Something(":(", e);
}
}
#Override
public void close()
{
if(lock.isHeldByCurrentThread())
{
lock.unlock();
}
}
}
One problem with this code is that no items are ever removed from the lockMap, and I don't know how to do this thread-safe. The following is definitely not thread-safe:
#Override
public void close()
{
if (lock.isHeldByCurrentThread())
{
if (lock.getQueueLength() == 1) // todo: getQueueLength is meant for system monitoring purposes only
{
lockMap.remove(key); // todo: not thread-safe, queuelength could have changed by now
}
lock.unlock();
}
}
the documentation for getQueueLength:
Returns an estimate of the number of threads waiting to
acquire this lock. The value is only an estimate because the number of
threads may change dynamically while this method traverses
internal data structures. This method is designed for use in
monitoring of the system state, not for synchronization
control.
Does anyone know a solution for this? Are there different strategies to achieve my goal?
After some more experimentation I came up with the code below, can anyone comment on whether this is a good approach and the code is correct?
public class Gatekeeper implements AutoCloseable
{
private static final ConcurrentMap<Long, ReentrantLock> lockMap = new ConcurrentHashMap<>();
private final ReentrantLock lock;
private final Long key;
private static final ConcurrentMap<Long, Integer> claimsPerLock = new ConcurrentHashMap<>();
private static final Object mutex = new Object();
public Gatekeeper(Long key)
{
this.key = key;
synchronized (mutex)
{
lock = lockMap.computeIfAbsent(key, (Long absentKey) -> new ReentrantLock(true));
claimsPerLock.compute(key, (k, oldValue) -> oldValue == null ? 1 : ++oldValue);
}
try
{
if(!lock.tryLock(30, TimeUnit.SECONDS))
{
throw new SomeException("Timeout occurred while trying to acquire lock");
}
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
throw new SomeException("Interrupted", e);
}
}
#Override
public void close()
{
lock.unlock();
synchronized (mutex)
{
claimsPerLock.compute(key, (k, oldValue) -> oldValue == null ? 0 : --oldValue);
if (claimsPerLock.get(key) <= 0)
{
lockMap.remove(key);
claimsPerLock.remove(key);
}
}
}
}

How to add and remove from HashMap for many writers and many readers (threads)?

Is it possible to solve this code without keyword synchronized?
Can we use ConcurrentHashMap or better HashMap with synchronized keyword (for methods)?
Or better ConcurrentHashMap (for iterating) with synchronized keyword (for methods)?
This is critical section code, reader thread get statistic and when decrement value (if value is zero, remove statistic but parallel writer thread may incement value). How to properly solve this?
Statistic statistic = data.get(id);
if (statistic != null) {
statistic.dec();
if (statistic.getValue() <= 0) {
data.remove(id);
}
public class Main {
private static final Map<Long, Statistic> data = new ConcurrentHashMap<>();
public static void main(String... args) throws IOException {
new Thread(() -> todoRunInWriterThread(1L)).start();
new Thread(() -> todoRunInReaderThread(1L)).start();
System.in.read();
}
//Many writers write some statistics
private static void todoRunInWriterThread(long id) {
Statistic statistic = data.get(id);
if (statistic == null) {
statistic = new Statistic();
data.put(id, statistic);
}
statistic.inc();
}
//Many readers read statistic and decrement value,
//if statistic value is zero (remove statistic)
private static void todoRunInReaderThread(long id) {
Statistic statistic = data.get(id);
if (statistic != null) {
statistic.dec();
if (statistic.getValue() <= 0) {
data.remove(id);
}
}
}
public static class Statistic {
private AtomicLong value = new AtomicLong(0);
public long getValue() {
return value.longValue();
}
public void inc() {
value.incrementAndGet();
}
public void dec() {
value.decrementAndGet();
}
}
}
I believe you should use ConcurrentHashMap. It has good performance in most cases, and your case for writer thread (get ... check if null ... put) can be resolved with ConcurrentHashMap#computeIfAbsent -> it will handle all locking internally.
Also please do some research about how ConcurrentHashMap works. it's not simply using synchronized keyword for each method. There's some striping locking involved, which is really good for performance

Safe publication of a ConcurrentHashMap into a class field

I am trying to write a test that demonstrates that assigning a new reference to a class' field in a multi-threading environment is not thread-safe and more specifically has visibility problems if that field is not declared as volatile or AtomicReference.
The scenario I use is a PropertiesLoader class (shown below), which is supposed to load a set of properties (currently only one property is used) stored in a Map<String, String> and also tries to support reloading. So there are many threads reading a property and at some point in time another thread is reloading a new value that needs to be visible to the reading threads.
The test is intended to work as following:
it invokes the reader threads which are spin-waiting until they "see"
the property value change
at some point the writer thread creates a new map with a new value for the property and assigns that map to the field in question (PropertyLoader.propertiesMap)
if all reader threads see the new value the test is completed otherwise it hangs forever.
Now I know that strictly speaking, there is no test that can prove the thread-safeness of some code (or the lack of it) but in this case I feel like it should be relatively easy to demonstrate the problem at least empirically.
I have tried using a HashMap implementation to store the properties and in this case the test hangs as expected even if I use only one reading thread.
If however, a ConcurrentHashMap implementation is used, the test never hangs no matter how many reading threads are being used (I have also tried waiting randomly in the reader threads with no success).
As far as my understanding goes, the fact that ConcurrentHashMap is thread-safe should not affect the visibility of the field where it is assigned to. So volatile/AtomicReference is still required for that field. However the above test seems to contradicts this since it behaves as if the map is always safely published without the need of additional synchronization.
Is my understanding wrong? Perhaps ConcurrentHashMap makes some additional synchronization promises that I am not aware of?
Any help would be highly appreciated.
P.S. The code below should be executable as is as a Junit test. I have run it in a machine with AMD Ryzen 5, Windows 10, JDK 1.8.0_201 and in a second machine i7 Intel, Fedora 30, JDK 1.8.xx (not remember the exact version of JDK) with the same results.
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
public class PropertiesLoaderTest {
private static final String NEW_VALUE = "newValue";
private static final String OLD_VALUE = "oldValue";
private static final String PROPERTY = "property";
/**
* Controls if the reference we are testing for visibility issues ({#link PropertiesLoader#propertyMap} will
* be assigned a HashMap or ConcurrentHashMap implementation during {#link PropertiesLoader#load(boolean)}
*/
private static boolean USE_SIMPLE_MAP = false;
#Test
public void testReload() throws Exception {
PropertiesLoader loader = new PropertiesLoader();
Random random = new Random();
int readerThreads = 5;
int totalThreads = readerThreads + 1;
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch finishLatch = new CountDownLatch(totalThreads);
// start reader threads that read the property trying to see the new property value
for (int i = 0; i < readerThreads; i++) {
startThread("reader-thread-" + i, startLatch, finishLatch, () -> {
while (true) {
String value = loader.getProperty(PROPERTY);
if (NEW_VALUE.equals(value)) {
log("Saw new value: " + value + " for property: " + PROPERTY);
break;
}
}
});
}
// start writer thread (i.e. the thread that reloads the properties)
startThread("writer-thread", startLatch, finishLatch, () -> {
Thread.sleep(random.nextInt(500));
log("starting reload...");
loader.reloadProperties();
log("finished reload...");
});
log("Firing " + readerThreads + " threads and 1 writer thread...");
startLatch.countDown();
log("Waiting for all threads to finish...");
finishLatch.await();
log("All threads finished. Test successful");
}
static class PropertiesLoader {
// The reference in question: this is assigned in the constructor and again when calling reloadProperties()
// It is not volatile nor AtomicReference so there are visibility concerns
Map<String, String> propertyMap;
PropertiesLoader() {
this.propertyMap = load(false);
}
public void reloadProperties() {
this.propertyMap = load(true);
}
public String getProperty(String propertyName) {
return propertyMap.get(propertyName);
}
private static Map<String, String> load(boolean isReload) {
// using a simple HashMap always hang the test as expected: the new reference cannot be
// seen by the reader thread
// using a ConcurrentHashMap always allow the test to finish no matter how many reader
// threads are used
Map<String, String> newMap = USE_SIMPLE_MAP ? new HashMap<>() : new ConcurrentHashMap<>();
newMap.put(PROPERTY, isReload ? NEW_VALUE : OLD_VALUE);
return newMap;
}
}
static void log(String msg) {
//System.out.println(Thread.currentThread().getName() + " - " + msg);
}
static void startThread(String name, CountDownLatch start, CountDownLatch finish, ThreadTask task) {
Thread t = new Thread(new ThreadTaskRunner(name, start, finish, task));
t.start();
}
#FunctionalInterface
interface ThreadTask {
void execute() throws Exception;
}
static class ThreadTaskRunner implements Runnable {
final CountDownLatch start;
final CountDownLatch finish;
final ThreadTask task;
final String name;
protected ThreadTaskRunner(String name, CountDownLatch start, CountDownLatch finish, ThreadTask task) {
this.start = start;
this.finish = finish;
this.task = task;
this.name = name;
}
#Override
public void run() {
try {
Thread.currentThread().setName(name);
start.await();
log("thread started");
task.execute();
log("thread finished successfully");
} catch (Exception e) {
log("Error: " + e.getMessage());
}
finish.countDown();
}
}
}
It's a bit worse than you might think but there is also a saving grace.
The bit worse part: constructors are not synchronized. In this case that means that the PropertiesLoader.propertyMap which is created in the constructor is not guaranteed to be visible to the other threads (reader or writer). Your saving grace here is the CountDownLatches you use (these establish a happen-before relation) as well as the Thread.start (which also establish a happen-before relation) . Also, in practice "constructors are not synchronized" is rarely a problem and difficult to reproduce (see also test-code below). For more information on the matter, please read this question. Conclusion is that the PropertiesLoader.propertyMap must either be volatile / AtomicReference or final (final could be used in combination with the ConcurrentHashMap).
The reason you cannot reproduce the synchronization issue with a ConcurrentHashMap is the same reason it is difficult to reproduce the "constructors are not synchronized" problem: a ConcurrentHashMap uses synchronization internally (see this answer) which triggers a memory flush that not only makes the new values in the map visible to other threads, but also the new PropertiesLoader.propertyMap value.
Note that a volatile PropertiesLoader.propertyMap will guarantee (and not just make it likely) that new values are visible to other threads (ConcurrentHashMap is not required, see also this answer). I usually set these kind of maps to a read-only map (with the help of Collections.unmodifiableMap()) to broadcast to other programmers that this is not an ordinary map that can be updated or changed at will.
Below some more test-code which tries to eliminate as much synchronization as possible. The end-result for the test is exactly the same but it also shows the side-effect of having a volatile boolean in a loop and that the non-null assignment of propertyMap somehow is always seen by other threads.
package so;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
public class MapVisibility {
static int readerThreadsAmount = 2;
public static void main(String[] args) {
ExecutorService executors = Executors.newFixedThreadPool(readerThreadsAmount);
try {
new MapVisibility().run(executors);
} catch (Exception e) {
e.printStackTrace();
} finally {
executors.shutdownNow(); // Does not work on FAIL, manually kill reader-task from task-manager.
}
}
//final boolean useConcurrentMap = false;
// When ConcurrentHashMap is used, test is always a success.
final boolean useConcurrentMap = true;
final boolean useStopBoolean = false;
// When volatile stop boolean is used, test is always a success.
//final boolean useStopBoolean = true;
//final boolean writeToConsole = false;
// Writing to System.out is synchronized, this can make a test succeed that would otherwise fail.
final boolean writeToConsole = true;
Map<String, String> propertyMap;
// When the map is volatile, test is always a success.
//volatile Map<String, String> propertyMap;
final String oldValue = "oldValue";
final String newValue = "newValue";
final String key = "key";
volatile boolean stop;
void run(ExecutorService executors) throws Exception {
IntStream.range(0, readerThreadsAmount).forEach(i -> {
executors.execute(new MapReader());
});
sleep(500); // give readers a chance to start
setMap(oldValue);
sleep(100); // give readers a chance to read map
setMap(newValue);
sleep(100); // give readers a chance to read new value in new map
executors.shutdown();
if (!executors.awaitTermination(100L, TimeUnit.MILLISECONDS)) {
System.out.println("FAIL");
stop = true;
} else {
System.out.println("Success");
}
}
void setMap(String value) {
Map<String, String> newMap = (useConcurrentMap ? new ConcurrentHashMap<>() : new HashMap<>());
newMap.put(key, value);
propertyMap = newMap;
}
class MapReader implements Runnable {
#Override
public void run() {
print("Reader started.");
final long startTime = System.currentTimeMillis();
while (propertyMap == null) {
// In worse case, this loop should never exit but it always does.
// No idea why.
sleep(1);
}
print((System.currentTimeMillis() - startTime) + " Reader got map.");
if (useStopBoolean) {
while (!stop) {
if (newValue.equals(propertyMap.get(key))) {
break;
}
}
} else {
while (true) {
if (newValue.equals(propertyMap.get(key))) {
break;
}
}
}
print((System.currentTimeMillis() - startTime) + " Reader got new value.");
}
}
void print(String msg) {
if (writeToConsole) {
System.out.println(msg);
}
}
void sleep(int timeout) {
// instead of using Thread.sleep, do some busy-work instead.
final long startTime = System.currentTimeMillis();
Random r = new Random();
#SuppressWarnings("unused")
long loopCount = 0;
while (System.currentTimeMillis() - startTime < timeout) {
for (int i = 0; i < 100_000; i++) {
double d = r.nextDouble();
double v = r.nextDouble();
#SuppressWarnings("unused")
double dummy = d / v;
}
loopCount++;
}
//print("Loops: " + loopCount);
}
}

Simple Java name based locks?

MySQL has a handy function:
SELECT GET_LOCK("SomeName")
This can be used to create simple, but very specific, name-based locks for an application. However, it requires a database connection.
I have many situations like:
someMethod() {
// do stuff to user A for their data for feature X
}
It doesn't make sense to simply synchronize this method, because, for example, if this method is called for user B in the meantime, user B does not need to wait for user A to finish before it starts, only operations for the user A and feature X combination need to wait.
With the MySql lock I could do something like:
someMethod() {
executeQuery("SELECT GET_LOCK('userA-featureX')")
// only locked for user A for their data for feature X
executeQuery("SELECT RELEASE_LOCK('userA-featureX')")
}
Since Java locking is based on objects, it seems like I would need to create a new object to represent the situation for this lock and then put it in a static cache somewhere so all the threads can see it. Subsequent requests to lock for that situation would then locate the lock object in the cache and acquire its lock. I tried to create something like this, but then the lock cache itself needs synchronization. Also, it is difficult to detect when a lock object is no longer being used so that it can be removed from the cache.
I have looked at the Java concurrent packages, but nothing stands out as being able to handle something like this. Is there an easy way to implement this, or am I looking at this from the wrong perspective?
Edit:
To clarify, I am not looking to create a predefined pool of locks ahead of time, I would like to create them on demand. Some pseudo-code for what I am thinking of is:
LockManager.acquireLock(String name) {
Lock lock;
synchronized (map) {
lock = map.get(name);
// doesn't exist yet - create and store
if(lock == null) {
lock = new Lock();
map.put(name, lock);
}
}
lock.lock();
}
LockManager.releaseLock(String name) {
// unlock
// if this was the last hold on the lock, remove it from the cache
}
All those answers I see are way too complicated. Why not simply use:
public void executeInNamedLock(String lockName, Runnable runnable) {
synchronized(lockName.intern()) {
runnable.run();
}
}
The key point is the method intern: it ensures that the String returned is a global unique object, and so it can be used as a vm-instance-wide mutex. All interned Strings are held in a global pool, so that's your static cache you were talking about in your original question. Don't worry about memleaks; those strings will be gc'ed if no other thread references it. Note however, that up to and including Java6 this pool is kept in PermGen space instead of the heap, so you might have to increase it.
There's a problem though if some other code in your vm locks on the same string for completely different reasons, but a) this is very unlikely, and b) you can get around it by introducing namespaces, e.g. executeInNamedLock(this.getClass().getName() + "_" + myLockName);
Can you have a Map<String, java.util.concurrent.Lock>? Each time you require a lock, you basically call map.get(lockName).lock().
Here's an example using Google Guava:
Map<String, Lock> lockMap = new MapMaker().makeComputingMap(new Function<String, Lock>() {
#Override public Lock apply(String input) {
return new ReentrantLock();
}
});
Then lockMap.get("anyOldString") will cause a new lock to be created if required and returned to you. You can then call lock() on that lock. makeComputingMap returns a Map that is thread-safe, so you can just share that with all your threads.
// pool of names that are being locked
HashSet<String> pool = new HashSet<String>();
lock(name)
synchronized(pool)
while(pool.contains(name)) // already being locked
pool.wait(); // wait for release
pool.add(name); // I lock it
unlock(name)
synchronized(pool)
pool.remove(name);
pool.notifyAll();
maybe this is useful for you: jkeylockmanager
Edit:
My initial response was probably a bit short. I am the author and was faced with this problem several times and could not find an existing solution. That's why I made this small library on Google Code.
Maybe a little later but you can use Google Guava Striped
Conceptually, lock striping is the technique of dividing a lock into many stripes, increasing the granularity of a single lock and allowing independent operations to lock different stripes and proceed concurrently, instead of creating contention for a single lock.
//init
stripes=Striped.lazyWeakLock(size);
//or
stripes=Striped.lock(size);
//...
Lock lock=stripes.get(object);
For locking on something like a user name, in-memory Locks in a map might be a bit leaky. As an alternative, you could look at using WeakReferences with WeakHashMap to create mutex objects that can be garbage collected when nothing refers to them. This avoids you having to do any manual reference counting to free up memory.
You can find an implementation here. Note that if you're doing frequent lookups on the map you may run into contention issues acquiring the mutex.
A generic solution using java.util.concurrent
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
public class LockByName<L> {
ConcurrentHashMap<String, L> mapStringLock;
public LockByName(){
mapStringLock = new ConcurrentHashMap<String, L>();
}
public LockByName(ConcurrentHashMap<String, L> mapStringLock){
this.mapStringLock = mapStringLock;
}
#SuppressWarnings("unchecked")
public L getLock(String key) {
L initValue = (L) createIntanceLock();
L lock = mapStringLock.putIfAbsent(key, initValue);
if (lock == null) {
lock = initValue;
}
return lock;
}
protected Object createIntanceLock() {
return new ReentrantLock();
}
public static void main(String[] args) {
LockByName<ReentrantLock> reentrantLocker = new LockByName<ReentrantLock>();
ReentrantLock reentrantLock1 = reentrantLocker.getLock("pepe");
try {
reentrantLock1.lock();
//DO WORK
}finally{
reentrantLock1.unlock();
}
}
}
Based on the answer of McDowell and his class IdMutexProvider, I have written the generic class LockMap that uses WeakHashMap to store lock objects. LockMap.get() can be used to retrieve a lock object for a key, which can then be used with the Java synchronized (...) statement to apply a lock. Unused lock objects are automatically freed during garbage collection.
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
// A map that creates and stores lock objects for arbitrary keys values.
// Lock objects which are no longer referenced are automatically released during garbage collection.
// Author: Christian d'Heureuse, www.source-code.biz
// Based on IdMutexProvider by McDowell, http://illegalargumentexception.blogspot.ch/2008/04/java-synchronizing-on-transient-id.html
// See also https://stackoverflow.com/questions/5639870/simple-java-name-based-locks
public class LockMap<KEY> {
private WeakHashMap<KeyWrapper<KEY>,WeakReference<KeyWrapper<KEY>>> map;
public LockMap() {
map = new WeakHashMap<KeyWrapper<KEY>,WeakReference<KeyWrapper<KEY>>>(); }
// Returns a lock object for the specified key.
public synchronized Object get (KEY key) {
if (key == null) {
throw new NullPointerException(); }
KeyWrapper<KEY> newKeyWrapper = new KeyWrapper<KEY>(key);
WeakReference<KeyWrapper<KEY>> ref = map.get(newKeyWrapper);
KeyWrapper<KEY> oldKeyWrapper = (ref == null) ? null : ref.get();
if (oldKeyWrapper != null) {
return oldKeyWrapper; }
map.put(newKeyWrapper, new WeakReference<KeyWrapper<KEY>>(newKeyWrapper));
return newKeyWrapper; }
// Returns the number of used entries in the map.
public synchronized int size() {
return map.size(); }
// KeyWrapper wraps a key value and is used in three ways:
// - as the key for the internal WeakHashMap
// - as the value for the internal WeakHashMap, additionally wrapped in a WeakReference
// - as the lock object associated to the key
private static class KeyWrapper<KEY> {
private KEY key;
private int hashCode;
public KeyWrapper (KEY key) {
this.key = key;
hashCode = key.hashCode(); }
public boolean equals (Object obj) {
if (obj == this) {
return true; }
if (obj instanceof KeyWrapper) {
return ((KeyWrapper)obj).key.equals(key); }
return false; }
public int hashCode() {
return hashCode; }}
} // end class LockMap
Example of how to use the LockMap class:
private static LockMap<String> lockMap = new LockMap<String>();
synchronized (lockMap.get(name)) {
...
}
A simple test program for the LockMap class:
public static Object lock1;
public static Object lock2;
public static void main (String[] args) throws Exception {
System.out.println("TestLockMap Started");
LockMap<Integer> map = new LockMap<Integer>();
lock1 = map.get(1);
lock2 = map.get(2);
if (lock2 == lock1) {
throw new Error(); }
Object lock1b = map.get(1);
if (lock1b != lock1) {
throw new Error(); }
if (map.size() != 2) {
throw new Error(); }
for (int i=0; i<10000000; i++) {
map.get(i); }
System.out.println("Size before gc: " + map.size()); // result varies, e.g. 4425760
System.gc();
Thread.sleep(1000);
if (map.size() != 2) {
System.out.println("Size after gc should be 2 but is " + map.size()); }
System.out.println("TestLockMap completed"); }
If anyone knows a better way to automatically test the LockMap class, please write a comment.
I'd like to notice that ConcurrentHashMap has built-in locking facility that is enough for simple exclusive multithread lock. No additional Lock objects needed.
Here is an example of such lock map used to enforce at most one active jms processing for single client.
private static final ConcurrentMap<String, Object> lockMap = new ConcurrentHashMap<String, Object>();
private static final Object DUMMY = new Object();
private boolean tryLock(String key) {
if (lockMap.putIfAbsent(key, DUMMY) != null) {
return false;
}
try {
if (/* attempt cluster-wide db lock via select for update nowait */) {
return true;
} else {
unlock(key);
log.debug("DB is already locked");
return false;
}
} catch (Throwable e) {
unlock(key);
log.debug("DB lock failed", e);
return false;
}
}
private void unlock(String key) {
lockMap.remove(key);
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void onMessage(Message message) {
String key = getClientKey(message);
if (tryLock(key)) {
try {
// handle jms
} finally {
unlock(key);
}
} else {
// key is locked, forcing redelivery
messageDrivenContext.setRollbackOnly();
}
}
2 years later but I was looking for a simple named locker solution and came across this, was usefull but I needed a simpler answer, so below what I came up with.
Simple lock under some name and release again under that same name.
private void doTask(){
locker.acquireLock(name);
try{
//do stuff locked under the name
}finally{
locker.releaseLock(name);
}
}
Here is the code:
public class NamedLocker {
private ConcurrentMap<String, Semaphore> synchSemaphores = new ConcurrentHashMap<String, Semaphore>();
private int permits = 1;
public NamedLocker(){
this(1);
}
public NamedLocker(int permits){
this.permits = permits;
}
public void acquireLock(String... key){
Semaphore tempS = new Semaphore(permits, true);
Semaphore s = synchSemaphores.putIfAbsent(Arrays.toString(key), tempS);
if(s == null){
s = tempS;
}
s.acquireUninterruptibly();
}
public void releaseLock(String... key){
Semaphore s = synchSemaphores.get(Arrays.toString(key));
if(s != null){
s.release();
}
}
}
Many implementations but non similar to mine.
Called my Dynamic lock implementation as ProcessDynamicKeyLock because it's a single process lock, for any object as key (equals+hashcode for uniqueness).
TODO: Add a way to provide the actual lock, for example, ReentrantReadWriteLock instead of ReentrantLock.
Implementation:
public class ProcessDynamicKeyLock<T> implements Lock
{
private final static ConcurrentHashMap<Object, LockAndCounter> locksMap = new ConcurrentHashMap<>();
private final T key;
public ProcessDynamicKeyLock(T lockKey)
{
this.key = lockKey;
}
private static class LockAndCounter
{
private final Lock lock = new ReentrantLock();
private final AtomicInteger counter = new AtomicInteger(0);
}
private LockAndCounter getLock()
{
return locksMap.compute(key, (key, lockAndCounterInner) ->
{
if (lockAndCounterInner == null) {
lockAndCounterInner = new LockAndCounter();
}
lockAndCounterInner.counter.incrementAndGet();
return lockAndCounterInner;
});
}
private void cleanupLock(LockAndCounter lockAndCounterOuter)
{
if (lockAndCounterOuter.counter.decrementAndGet() == 0)
{
locksMap.compute(key, (key, lockAndCounterInner) ->
{
if (lockAndCounterInner == null || lockAndCounterInner.counter.get() == 0) {
return null;
}
return lockAndCounterInner;
});
}
}
#Override
public void lock()
{
LockAndCounter lockAndCounter = getLock();
lockAndCounter.lock.lock();
}
#Override
public void unlock()
{
LockAndCounter lockAndCounter = locksMap.get(key);
lockAndCounter.lock.unlock();
cleanupLock(lockAndCounter);
}
#Override
public void lockInterruptibly() throws InterruptedException
{
LockAndCounter lockAndCounter = getLock();
try
{
lockAndCounter.lock.lockInterruptibly();
}
catch (InterruptedException e)
{
cleanupLock(lockAndCounter);
throw e;
}
}
#Override
public boolean tryLock()
{
LockAndCounter lockAndCounter = getLock();
boolean acquired = lockAndCounter.lock.tryLock();
if (!acquired)
{
cleanupLock(lockAndCounter);
}
return acquired;
}
#Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
{
LockAndCounter lockAndCounter = getLock();
boolean acquired;
try
{
acquired = lockAndCounter.lock.tryLock(time, unit);
}
catch (InterruptedException e)
{
cleanupLock(lockAndCounter);
throw e;
}
if (!acquired)
{
cleanupLock(lockAndCounter);
}
return acquired;
}
#Override
public Condition newCondition()
{
LockAndCounter lockAndCounter = locksMap.get(key);
return lockAndCounter.lock.newCondition();
}
}
Simple test:
public class ProcessDynamicKeyLockTest
{
#Test
public void testDifferentKeysDontLock() throws InterruptedException
{
ProcessDynamicKeyLock<Object> lock = new ProcessDynamicKeyLock<>(new Object());
lock.lock();
AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
try
{
new Thread(() ->
{
ProcessDynamicKeyLock<Object> anotherLock = new ProcessDynamicKeyLock<>(new Object());
anotherLock.lock();
try
{
anotherThreadWasExecuted.set(true);
}
finally
{
anotherLock.unlock();
}
}).start();
Thread.sleep(100);
}
finally
{
Assert.assertTrue(anotherThreadWasExecuted.get());
lock.unlock();
}
}
#Test
public void testSameKeysLock() throws InterruptedException
{
Object key = new Object();
ProcessDynamicKeyLock<Object> lock = new ProcessDynamicKeyLock<>(key);
lock.lock();
AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
try
{
new Thread(() ->
{
ProcessDynamicKeyLock<Object> anotherLock = new ProcessDynamicKeyLock<>(key);
anotherLock.lock();
try
{
anotherThreadWasExecuted.set(true);
}
finally
{
anotherLock.unlock();
}
}).start();
Thread.sleep(100);
}
finally
{
Assert.assertFalse(anotherThreadWasExecuted.get());
lock.unlock();
}
}
}
Another possible solution which I have implemented and tested when encountered the same requirements as the original poster.
In this solution:
No external libraries
Not leaving unused objects in memory
Minimal usage of synchronized and minimal "cross-names" locking
No downsides of using intern
Helper class code:
public class IdBasedLockHelper<T> {
private final static AtomicIntegerWithEquals zero = new AtomicIntegerWithEquals(0);
private final ConcurrentMap<T, AtomicIntegerWithEquals> identifierToLockCounter = new ConcurrentHashMap<>();
public void executeLocked(T lockId, Runnable runnable) {
AtomicIntegerWithEquals counterAndLock = identifierToLockCounter.compute(lockId, (key, existing) -> {
if (existing == null) {
return new AtomicIntegerWithEquals(1);
}
existing.atomicValue.incrementAndGet();
return existing;
});
synchronized (counterAndLock) {
try {
runnable.run();
} finally {
counterAndLock.atomicValue.decrementAndGet();
identifierToLockCounter.remove(lockId, zero);
}
}
}
// AtomicInteger does not implement equals() properly so there is a need for such wrapper
private static class AtomicIntegerWithEquals {
private final AtomicInteger atomicValue;
AtomicIntegerWithEquals(int value) {
this.atomicValue = new AtomicInteger(value);
}
// Used internally by remove()
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof IdBasedLockHelper.AtomicIntegerWithEquals)) return false;
return atomicValue.get() == ((AtomicIntegerWithEquals) o).atomicValue.get();
}
// Not really used, but when implementing custom equals() it is a good practice to implement also hashCode()
#Override
public int hashCode() {
return atomicValue.get();
}
}
}
Usage:
IdBasedLockHelper<String> idBasedLockHelper = new IdBasedLockHelper<>();
idBasedLockHelper.executeLocked("Some Name", () -> {
// Your code to execute synchronized per name
});
ConcurrentHashMap is used to store synchronization object for each lock id.
ConcurrentHashMap already provides compute and remove (if value equals) as atomic operations. The AtomicInteger inside the stored value counts the number of holds of the synchronization object and this allows removing it from the map only if it is not in use (number of holds equals 0).
Maybe something like that:
public class ReentrantNamedLock {
private class RefCounterLock {
public int counter;
public ReentrantLock sem;
public RefCounterLock() {
counter = 0;
sem = new ReentrantLock();
}
}
private final ReentrantLock _lock = new ReentrantLock();
private final HashMap<String, RefCounterLock> _cache = new HashMap<String, RefCounterLock>();
public void lock(String key) {
_lock.lock();
RefCounterLock cur = null;
try {
if (!_cache.containsKey(key)) {
cur = new RefCounterLock();
_cache.put(key, cur);
} else {
cur = _cache.get(key);
}
cur.counter++;
} finally {
_lock.unlock();
}
cur.sem.lock();
}
public void unlock(String key) {
_lock.lock();
try {
if (_cache.containsKey(key)) {
RefCounterLock cur = _cache.get(key);
cur.counter--;
cur.sem.unlock();
if (cur.counter == 0) { //last reference
_cache.remove(key);
}
cur = null;
}
} finally {
_lock.unlock();
}
}}
I didn't test it though.
After some disappointment that there is no language level support or simple Guava/Commons class for named locks,
This is what I settled down to:
ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();
Object getLock(String name) {
Object lock = locks.get(name);
if (lock == null) {
Object newLock = new Object();
lock = locks.putIfAbsent(name, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
void somethingThatNeedsNamedLocks(String name) {
synchronized(getLock(name)) {
// some operations mutually exclusive per each name
}
}
Here I achieved: little boilerplate code with no library dependency, atomically acquiring the lock object, not polluting the global interned string objects, no low-level notify/wait chaos and no try-catch-finally mess.
Similar to the answer from Lyomi, but uses the more flexible ReentrantLock instead of a synchronized block.
public class NamedLock
{
private static final ConcurrentMap<String, Lock> lockByName = new ConcurrentHashMap<String, Lock>();
public static void lock(String key)
{
Lock lock = new ReentrantLock();
Lock existingLock = lockByName.putIfAbsent(key, lock);
if(existingLock != null)
{
lock = existingLock;
}
lock.lock();
}
public static void unlock(String key)
{
Lock namedLock = lockByName.get(key);
namedLock.unlock();
}
}
Yes this will grow over time - but using the ReentrantLock opens up greater possibilities for removing the lock from the map. Although, removing items from the map doesn't seem all that useful considering removing values from the map will not shrink its size. Some manual map sizing logic would have to be implemented.
Memory consideration
Often times, synchronization needed for a particular key is short-lived. Keeping around released keys can lead to excessive memory waste, making it non-viable.
Here's an implementation does not internally keep around released keys.
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
public class KeyedMutexes<K> {
private final ConcurrentMap<K, CountDownLatch> key2Mutex = new ConcurrentHashMap<>();
public void lock(K key) throws InterruptedException {
final CountDownLatch ourLock = new CountDownLatch(1);
for (;;) {
CountDownLatch theirLock = key2Mutex.putIfAbsent(key, ourLock);
if (theirLock == null) {
return;
}
theirLock.await();
}
}
public void unlock(K key) {
key2Mutex.remove(key).countDown();
}
}
Reentrancy, and other bells and whistles
If one wants re-entrant lock semantics, they can extend the above by wrapping the mutex object in a class that keeps track of the locking thread and locked count.
e.g.:
private static class Lock {
final CountDownLatch mutex = new CountDownLatch(1);
final long threadId = Thread.currentThread().getId();
int lockedCount = 1;
}
If one wants lock() to return an object to make releases easier and safer, that's also a possibility.
Putting it all together, here's what the class could look like:
public class KeyedReentrantLocks<K> {
private final ConcurrentMap<K, KeyedLock> key2Lock = new ConcurrentHashMap<>();
public KeyedLock acquire(K key) throws InterruptedException {
final KeyedLock ourLock = new KeyedLock() {
#Override
public void close() {
if (Thread.currentThread().getId() != threadId) {
throw new IllegalStateException("wrong thread");
}
if (--lockedCount == 0) {
key2Lock.remove(key);
mutex.countDown();
}
}
};
for (;;) {
KeyedLock theirLock = key2Lock.putIfAbsent(key, ourLock);
if (theirLock == null) {
return ourLock;
}
if (theirLock.threadId == Thread.currentThread().getId()) {
theirLock.lockedCount++;
return theirLock;
}
theirLock.mutex.await();
}
}
public static abstract class KeyedLock implements AutoCloseable {
protected final CountDownLatch mutex = new CountDownLatch(1);
protected final long threadId = Thread.currentThread().getId();
protected int lockedCount = 1;
#Override
public abstract void close();
}
}
And here's how one might use it:
try (KeyedLock lock = locks.acquire("SomeName")) {
// do something critical here
}
In response to the suggestion of using new MapMaker().makeComputingMap()...
MapMaker().makeComputingMap() is deprecated for safety reasons. The successor is CacheBuilder. With weak keys/values applied to CacheBuilder, we're soooo close to a solution.
The problem is a note in CacheBuilder.weakKeys():
when this method is used, the resulting cache will use identity (==) comparison to determine equality of keys.
This makes it impossible to select an existing lock by string value. Erg.
(4 years later...)
My answer is similar to user2878608's but I think there are some missing edge cases in that logic. I also thought Semaphore was for locking multiple resources at once (though I suppose using it for counting lockers like that is fine too), so I used a generic POJO lock object instead. I ran one test on it which demonstrated each of the edge cases existed IMO and will be using it on my project at work. Hope it helps someone. :)
class Lock
{
int c; // count threads that require this lock so you don't release and acquire needlessly
}
ConcurrentHashMap<SomeKey, Lock> map = new ConcurrentHashMap<SomeKey, Lock>();
LockManager.acquireLock(String name) {
Lock lock = new Lock(); // creating a new one pre-emptively or checking for null first depends on which scenario is more common in your use case
lock.c = 0;
while( true )
{
Lock prevLock = map.putIfAbsent(name, lock);
if( prevLock != null )
lock = prevLock;
synchronized (lock)
{
Lock newLock = map.get(name);
if( newLock == null )
continue; // handles the edge case where the lock got removed while someone was still waiting on it
if( lock != newLock )
{
lock = newLock; // re-use the latest lock
continue; // handles the edge case where a new lock was acquired and the critical section was entered immediately after releasing the lock but before the current locker entered the sync block
}
// if we already have a lock
if( lock.c > 0 )
{
// increase the count of threads that need an offline director lock
++lock.c;
return true; // success
}
else
{
// safely acquire lock for user
try
{
perNameLockCollection.add(name); // could be a ConcurrentHashMap or other synchronized set, or even an external global cluster lock
// success
lock.c = 1;
return true;
}
catch( Exception e )
{
// failed to acquire
lock.c = 0; // this must be set in case any concurrent threads are waiting
map.remove(name); // NOTE: this must be the last critical thing that happens in the sync block!
}
}
}
}
}
LockManager.releaseLock(String name) {
// unlock
// if this was the last hold on the lock, remove it from the cache
Lock lock = null; // creating a new one pre-emptively or checking for null first depends on which scenario is more common in your use case
while( true )
{
lock = map.get(name);
if( lock == null )
{
// SHOULD never happen
log.Error("found missing lock! perhaps a releaseLock call without corresponding acquireLock call?! name:"+name);
lock = new Lock();
lock.c = 1;
Lock prevLock = map.putIfAbsent(name, lock);
if( prevLock != null )
lock = prevLock;
}
synchronized (lock)
{
Lock newLock = map.get(name);
if( newLock == null )
continue; // handles the edge case where the lock got removed while someone was still waiting on it
if( lock != newLock )
{
lock = newLock; // re-use the latest lock
continue; // handles the edge case where a new lock was acquired and the critical section was entered immediately after releasing the lock but before the current locker entered the sync block
}
// if we are not the last locker
if( lock.c > 1 )
{
// decrease the count of threads that need an offline director lock
--lock.c;
return true; // success
}
else
{
// safely release lock for user
try
{
perNameLockCollection.remove(name); // could be a ConcurrentHashMap or other synchronized set, or even an external global cluster lock
// success
lock.c = 0; // this must be set in case any concurrent threads are waiting
map.remove(name); // NOTE: this must be the last critical thing that happens in the sync block!
return true;
}
catch( Exception e )
{
// failed to release
log.Error("unable to release lock! name:"+name);
lock.c = 1;
return false;
}
}
}
}
}
I've created a tokenProvider based on the IdMutexProvider of McDowell.
The manager uses a WeakHashMap which takes care of cleaning up unused locks.
TokenManager:
/**
* Token provider used to get a {#link Mutex} object which is used to get exclusive access to a given TOKEN.
* Because WeakHashMap is internally used, Mutex administration is automatically cleaned up when
* the Mutex is no longer is use by any thread.
*
* <pre>
* Usage:
* private final TokenMutexProvider<String> myTokenProvider = new TokenMutexProvider<String>();
*
* Mutex mutex = myTokenProvider.getMutex("123456");
* synchronized (mutex) {
* // your code here
* }
* </pre>
*
* Class inspired by McDowell.
* url: http://illegalargumentexception.blogspot.nl/2008/04/java-synchronizing-on-transient-id.html
*
* #param <TOKEN> type of token. It is important that the equals method of that Object return true
* for objects of different instances but with the same 'identity'. (see {#link WeakHashMap}).<br>
* E.g.
* <pre>
* String key1 = "1";
* String key1b = new String("1");
* key1.equals(key1b) == true;
*
* or
* Integer key1 = 1;
* Integer key1b = new Integer(1);
* key1.equals(key1b) == true;
* </pre>
*/
public class TokenMutexProvider<TOKEN> {
private final Map<Mutex, WeakReference<Mutex>> mutexMap = new WeakHashMap<Mutex, WeakReference<Mutex>>();
/**
* Get a {#link Mutex} for the given (non-null) token.
*/
public Mutex getMutex(TOKEN token) {
if (token==null) {
throw new NullPointerException();
}
Mutex key = new MutexImpl(token);
synchronized (mutexMap) {
WeakReference<Mutex> ref = mutexMap.get(key);
if (ref==null) {
mutexMap.put(key, new WeakReference<Mutex>(key));
return key;
}
Mutex mutex = ref.get();
if (mutex==null) {
mutexMap.put(key, new WeakReference<Mutex>(key));
return key;
}
return mutex;
}
}
public int size() {
synchronized (mutexMap) {
return mutexMap.size();
}
}
/**
* Mutex for acquiring exclusive access to a token.
*/
public static interface Mutex {}
private class MutexImpl implements Mutex {
private final TOKEN token;
protected MutexImpl(TOKEN token) {
this.token = token;
}
#Override
public boolean equals(Object other) {
if (other==null) {
return false;
}
if (getClass()==other.getClass()) {
TOKEN otherToken = ((MutexImpl)other).token;
return token.equals(otherToken);
}
return false;
}
#Override
public int hashCode() {
return token.hashCode();
}
}
}
Usage:
private final TokenMutexManager<String> myTokenManager = new TokenMutexManager<String>();
Mutex mutex = myTokenManager.getMutex("UUID_123456");
synchronized(mutex) {
// your code here
}
or rather use Integers?
private final TokenMutexManager<Integer> myTokenManager = new TokenMutexManager<Integer>();
Mutex mutex = myTokenManager.getMutex(123456);
synchronized(mutex) {
// your code here
}
This thread is old, but a possible solution is the framework https://github.com/brandaof/named-lock.
NamedLockFactory lockFactory = new NamedLockFactory();
...
Lock lock = lockFactory.getLock("lock_name");
lock.lock();
try{
//manipulate protected state
}
finally{
lock.unlock();
}
Here is a simple and optimized solution which addresses the removal of used locks also, but with an overhead of synchronization of the Map:
public class NamedLock {
private Map<String, ReentrantLock> lockMap;
public NamedLock() {
lockMap = new HashMap<>();
}
public void lock(String... name) {
ReentrantLock newLock = new ReentrantLock(true);
ReentrantLock lock;
synchronized (lockMap) {
lock = Optional.ofNullable(lockMap.putIfAbsent(Arrays.toString(name), newLock)).orElse(newLock);
}
lock.lock();
}
public void unlock(String... name) {
ReentrantLock lock = lockMap.get(Arrays.toString(name));
synchronized (lockMap) {
if (!lock.hasQueuedThreads()) {
lockMap.remove(name);
}
}
lock.unlock();
}
}
Your idea about a shared static repository of lock objects for each situation is correct.
You don't need the cache itself to be synchronized ... it can be as simple as a hash map.
Threads can simultaneously get a lock object from the map. The actual synchronization logic should be encapsulated within each such object separately (see the java.util.concurrent package for that - http://download.oracle.com/javase/6/docs/api/java/util/concurrent/locks/package-summary.html)
TreeMap because in HashMap size of inner array can only increase
public class Locker<T> {
private final Object lock = new Object();
private final Map<T, Value> map = new TreeMap<T, Value>();
public Value<T> lock(T id) {
Value r;
synchronized (lock) {
if (!map.containsKey(id)) {
Value value = new Value();
value.id = id;
value.count = 0;
value.lock = new ReentrantLock();
map.put(id, value);
}
r = map.get(id);
r.count++;
}
r.lock.lock();
return r;
}
public void unlock(Value<T> r) {
r.lock.unlock();
synchronized (lock) {
r.count--;
if (r.count == 0)
map.remove(r.id);
}
}
public static class Value<T> {
private Lock lock;
private long count;
private T id;
}
}

Two-way communication with a Java thread

In my application I'm performing somewhat heavy lookup operations. These operations must be done within a single thread (persistence framework limitation).
I want to cache the results. Thus, I have a class UMRCache, with an inner class Worker:
public class UMRCache {
private Worker worker;
private List<String> requests = Collections.synchronizedList<new ArrayList<String>>());
private Map<String, Object> cache = Collections.synchronizedMap(new HashMap<String, Object>());
public UMRCache(Repository repository) {
this.worker = new Worker(repository);
this.worker.start();
}
public Object get(String key) {
if (this.cache.containsKey(key)) {
// If the element is already cached, get value from cache
return this.cache.get(key);
}
synchronized (this.requests) {
// Add request to queue
this.requests.add(key);
// Notify the Worker thread that there's work to do
this.requests.notifyAll();
}
synchronized (this.cache) {
// Wait until Worker has updated the cache
this.cache.wait();
// Now, cache should contain a value for key
return this.cache.get(key);
}
}
private class Worker extends Thread {
public void run() {
boolean doRun = true;
while (doRun) {
synchronized (requests) {
while (requests.isEmpty() && doRun) {
requests.wait(); // Wait until there's work to do
}
synchronized (cache) {
Set<String> processed = new HashSet<String>();
for (String key : requests) {
// Do the lookup
Object result = respository.lookup(key);
// Save to cache
cache.put(key, result);
processed.add(key);
}
// Remove processed requests from queue
requests.removeAll(processed);
// Notify all threads waiting for their requests to be served
cache.notifyAll();
}
}
}
}
}
}
I have a testcase for this:
public class UMRCacheTest extends TestCase {
private UMRCache umrCache;
public void setUp() throws Exception {
super.setUp();
umrCache = new UMRCache(repository);
}
public void testGet() throws Exception {
for (int i = 0; i < 10000; i++) {
final List fetched = Collections.synchronizedList(new ArrayList());
final String[] keys = new String[]{"key1", "key2"};
final String[] expected = new String[]{"result1", "result2"}
final Random random = new Random();
Runnable run1 = new Runnable() {
public void run() {
for (int i = 0; i < keys.length; i++) {
final String key = keys[i];
final Object result = umrCache.get(key);
assertEquals(key, results[i]);
fetched.add(um);
try {
Thread.sleep(random.nextInt(3));
} catch (InterruptedException ignore) {
}
}
}
};
Runnable run2 = new Runnable() {
public void run() {
for (int i = keys.length - 1; i >= 0; i--) {
final String key = keys[i];
final String result = umrCache.get(key);
assertEquals(key, results[i]);
fetched.add(um);
try {
Thread.sleep(random.nextInt(3));
} catch (InterruptedException ignore) {
}
}
}
};
final Thread thread1 = new Thread(run1);
thread1.start();
final Thread thread2 = new Thread(run2);
thread2.start();
final Thread thread3 = new Thread(run1);
thread3.start();
thread1.join();
thread2.join();
thread3.join();
umrCache.dispose();
assertEquals(6, fetched.size());
}
}
}
The test fails randomly, at about 1 out of 10 runs. It will fail at the last assertion: assertEquals(6, fetched.size()), at assertEquals(key, results[i]), or sometimes the test runner will never finish.
So there's something buggy about my thread logic. Any tips?
EDIT:
I might have cracked it now, thanks to all who have helped.
The solution seems to be:
public Object get(String key) {
if (this.cache.containsKey(key)) {
// If the element is already cached, get value from cache
return this.cache.get(key);
}
synchronized (this.requests) {
// Add request to queue
this.requests.add(key);
// Notify the Worker thread that there's work to do
this.requests.notifyAll();
}
synchronized (this.cache) {
// Wait until Worker has updated the cache
while (!this.cache.containsKey(key)) {
this.cache.wait();
}
// Now, cache should contain a value for key
return this.cache.get(key);
}
}
get() method logic can miss result and get stuck
synchronized (this.requests) {
// Add request to queue
this.requests.add(key);
// Notify the Worker thread that there's work to do
this.requests.notifyAll();
}
// ----- MOMENT1. If at this moment Worker puts result into cache it
// will be missed since notification will be lost
synchronized (this.cache) {
// Wait until Worker has updated the cache
this.cache.wait();
// ----- MOMENT2. May be too late, since cache notifiation happened before at MOMENT1
// Now, cache should contain a value for key
return this.cache.get(key);
}
The variable fetched in your test is an ArrayList and is accessed and updated from your two anonymous Runnable instances.
ArrayList is not thread safe, from the documentation:
Note that this implementation is not
synchronized. If multiple threads
access an ArrayList instance
concurrently, and at least one of the
threads modifies the list
structurally, it must be synchronized
externally. (A structural modification
is any operation that adds or deletes
one or more elements, or explicitly
resizes the backing array; merely
setting the value of an element is not
a structural modification.) This is
typically accomplished by
synchronizing on some object that
naturally encapsulates the list. If no
such object exists, the list should be
"wrapped" using the
Collections.synchronizedList method.
This is best done at creation time, to
prevent accidental unsynchronized
access to the list:
Hence I think your test needs a little adjusting.
I noticed your lookup in cache isn't atomic operation:
if (this.cache.containsKey(key)) {
// If the element is already cached, get value from cache
return this.cache.get(key);
}
Since you never delete from cache in your code, you always will get some value by this code. But if, in future, you plan to clean cache, lack of atomicity here will become a problem.

Categories