Thread synchronization based upon an id - java

I need a way to allow only one thread to modify data related to a service ticket. More than one thread may be attempting to modify the ticket data at the same time.
Below is a simplified version of my approach. Is there a better way to do this? Maybe with java.util.concurrent packages?
public class SomeClass1
{
static final HashMap<Integer, Object> ticketLockMap = new HashMap<Integer, Object>();
public void process(int ticketNumber)
{
synchronized (getTicketLock(ticketNumber))
{
// only one thread may modify ticket data here
// ... ticket modifications here...
}
}
protected static Object getTicketLock(int ticketNumber)
{
Object ticketLock;
// allow only one thread to use map
synchronized (ticketLockMap)
{
ticketLock = ticketLockMap.get(ticketNumber);
if (ticketLock == null)
{
// first time ticket is locked
ticketLock = new Object();
ticketLockMap.put(ticketNumber, ticketLock);
}
}
return ticketLock;
}
}
Additionally, if I don't want the HashMap filling up with unused locks, I would need a more complex approach like the following:
public class SomeClass2
{
static final HashMap<Integer, Lock> ticketLockMap = new HashMap<Integer, Lock>();
public void process(int ticketNumber)
{
synchronized (getTicketLock(ticketNumber))
{
// only one thread may modify ticket data here
// ... ticket modifications here...
// after all modifications, release lock
releaseTicketLock(ticketNumber);
}
}
protected static Lock getTicketLock(int ticketNumber)
{
Lock ticketLock;
// allow only one thread to use map
synchronized (ticketLockMap)
{
ticketLock = ticketLockMap.get(ticketNumber);
if (ticketLock == null)
{
// first time ticket is locked
ticketLock = new Lock();
ticketLockMap.put(ticketNumber, ticketLock);
}
}
return ticketLock;
}
protected static void releaseTicketLock(int ticketNumber)
{
// allow only one thread to use map
synchronized (ticketLockMap)
{
Lock ticketLock = ticketLockMap.get(ticketNumber);
if (ticketLock != null && --ticketLock.inUseCount == 0)
{
// lock no longer in use
ticketLockMap.remove(ticketLock);
}
}
}
}
class Lock
{
// constructor/getters/setters omitted for brevity
int inUseCount = 1;
}

You might be looking for the Lock interface. The second case could be solved by a ReentrantLock, which counts the number of times it has been locked.
Locks have a .lock() method which waits for the lock to acquire and an .unlock method which should be called like
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
This could then be combined with a HashMap<Integer, Lock>. You could omit the synchronized calls and cut down on lines of code.

Related

In Java how to synchronize cache read and write operations

I am working on implementing a simple cache using ArrayList in my application.
I would like to synchronize cache update operations, while updating the cache I should not allow to perform read operations. So once cache update is completed, then only cache should allow to read.
ContextManager.java
public class ContextManager{
private List<String> trashCanIds;
public List<String> getIds() {
return ids;
}
public void setIds(List<String> ids) {
this.ids = ids;
}
}
ConfigManager.java
public class ConfigManager{
ContextManager ctxManager = new ContextManager();
public synchronized List<String> loadIds() throws Exception {
Utils utils = new Utils();
List<String> listIds = null;
String[] ids = utils.fetchIds();
if(Objects.nonNull(ids) && ids.length > 0) {
listIds = new ArrayList<>(Arrays.asList(ids[0].split(",")));
}
ctxManager.setIds(idsList);
return idsList;
}
}
DeleteManager.java
public class DeleteManager {
ConfigManager configManager = new ConfigManager();
configManager.loadIds();
}
TestManager.java
public class TestManager {
ContextManager contextManager = new ContextManager();
contextManager.getIds();
}
In this code I have synchronized the loadIds() method.
Need help, how to prevent reading getIds() while loadIds() in progress.
You could achieve this by using the ReadWriteLock interface implemented with a ReentrantReadWriteLock instance. This class can represent your case of read and write by acquiring the corresponding lock when performing the getIds and loadIds operations. In fact,
A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing. The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html
Basically, your loadIds should acquire the write-lock before proceeding with its operations. If it succeeds, it immediately acquires the lock and carries on with its computation; otherwise it blocks the corresponding thread until the lock is obtained or an InterruptedException is thrown.
On the other hand, the getIds method should acquire the read-lock instead. Where the current thread immediately obtains the lock if this is available; otherwise it blocks the corresponding thread until the lock is obtained or an InterruptedException is thrown.
ContextManager.java
public class ContextManager{
private List<String> trashCanIds;
private ReadWriteLock lock;
private Lock readLock;
private Lock writeLock;
public ContextManager(){
lock = new ReentrantReadWriteLock(true);
readLock = lock.readLock();
writeLock = lock.writeLock();
}
public List<String> getIds() {
readLock.lock();
try {
List<String> tempTrashCanIds = new ArrayList(trashCanIds);
} finally {
readLock.unlock();
}
return tempTrashCanIds;
}
public void setIds(List<String> ids) {
this.ids = ids;
}
public void readLock(){
this.readLock.lock();
}
public void readUnlock(){
this.readLock.unlock();
}
public void writeLock(){
this.writeLock.lock();
}
public void writeUnlock(){
this.writeLock.unlock();
}
}
ConfigManager.java
public class ConfigManager{
ContextManager ctxManager = new ContextManager();
public List<String> loadIds() throws Exception {
Utils utils = new Utils();
List<String> listIds = null;
String[] ids = utils.fetchIds();
if(Objects.nonNull(ids) && ids.length > 0) {
listIds = new ArrayList<>(Arrays.asList(ids[0].split(",")));
}
ctxManager.writeLock();
try {
ctxManager.setIds(idsList);
} finally {
ctxManager.writeUnlock();
}
return idsList;
}
}

Does synchronisation with ConcurrentHashMap's .compute() guarantee visibility?

Within ConcurrentHashMap.compute() I increment and decrement some long value located in shared memory. Read, increment/decrement only gets performed within compute method on the same key.
So the access to long value is synchronised by locking on ConcurrentHashMap segment, thus increment/decrement is atomic. My question is: Does this synchronisation on a map guarantee visibility for long value? Can I rely on Map's internal synchronisation or should I make my long value volatile?
I know that when you explicitly synchronise on a lock, visibility is guaranteed. But I do not have perfect understanding of ConcurrentHashMap internals. Or maybe I can trust it today but tomorrow ConcurrentHashMap's internals may somehow change: exclusive access will be preserved, but visibility will disappear... and it is an argument to make my long value volatile.
Below I will post a simplified example. According to the test there is no race condition today. But can I trust this code long-term without volatile for long value?
class LongHolder {
private final ConcurrentMap<Object, Object> syncMap = new ConcurrentHashMap<>();
private long value = 0;
public void increment() {
syncMap.compute("1", (k, v) -> {
if (++value == 2000000) {
System.out.println("Expected final state. If this gets printed, this simple test did not detect visibility problem");
}
return null;
});
}
}
class IncrementRunnable implements Runnable {
private final LongHolder longHolder;
IncrementRunnable(LongHolder longHolder) {
this.longHolder = longHolder;
}
#Override
public void run() {
for (int i = 0; i < 1000000; i++) {
longHolder.increment();
}
}
}
public class ConcurrentMapExample {
public static void main(String[] args) throws InterruptedException {
LongHolder longholder = new LongHolder();
Thread t1 = new Thread(new IncrementRunnable(longholder));
Thread t2 = new Thread(new IncrementRunnable(longholder));
t1.start();
t2.start();
}
}
UPD: adding another example which is closer to the code I am working on. I would like to remove map entries when no one else is using the object. Please note that reading and writing of the long value happens only inside of remapping function of ConcurrentHashMap.compute:
public class ObjectProvider {
private final ConcurrentMap<Long, CountingObject> map = new ConcurrentHashMap<>();
public CountingObject takeObjectForId(Long id) {
return map.compute(id, (k, v) -> {
CountingObject returnLock;
returnLock = v == null ? new CountingObject() : v;
returnLock.incrementUsages();
return returnLock;
});
}
public void releaseObjectForId(Long id, CountingObject o) {
map.compute(id, (k, v) -> o.decrementUsages() == 0 ? null : o);
}
}
class CountingObject {
private int usages;
public void incrementUsages() {
--usages;
}
public int decrementUsages() {
return --usages;
}
}
UPD2: I admit that I failed to provide the simplest code examples previously, posting a real code now:
public class LockerUtility<T> {
private final ConcurrentMap<T, CountingLock> locks = new ConcurrentHashMap<>();
public void executeLocked(T entityId, Runnable synchronizedCode) {
CountingLock lock = synchronizedTakeEntityLock(entityId);
try {
lock.lock();
try {
synchronizedCode.run();
} finally {
lock.unlock();
}
} finally {
synchronizedReturnEntityLock(entityId, lock);
}
}
private CountingLock synchronizedTakeEntityLock(T id) {
return locks.compute(id, (k, l) -> {
CountingLock returnLock;
returnLock = l == null ? new CountingLock() : l;
returnLock.takeForUsage();
return returnLock;
});
}
private void synchronizedReturnEntityLock(T lockId, CountingLock lock) {
locks.compute(lockId, (i, v) -> lock.returnBack() == 0 ? null : lock);
}
private static class CountingLock extends ReentrantLock {
private volatile long usages = 0;
public void takeForUsage() {
usages++;
}
public long returnBack() {
return --usages;
}
}
}
No, this approach will not work, not even with volatile. You would have to use AtomicLong, LongAdder, or the like, to make this properly thread-safe. ConcurrentHashMap doesn't even work with segmented locks these days.
Also, your test does not prove anything. Concurrency issues by definition don't happen every time. Not even every millionth time.
You must use a proper concurrent Long accumulator like AtomicLong or LongAdder.
Do not get fooled by the line in the documentation of compute:
The entire method invocation is performed atomically
This does work for side-effects, like you have in that value++; it only works for the internal data of ConcurrentHashMap.
The first thing that you miss is that locking in CHM, the implementation has changed a lot (as the other answer has noted). But even if it did not, your understanding of the:
I know that when you explicitly synchronize on a lock, visibility is guaranteed
is flawed. JLS says that this is guaranteed when both the reader and the writer use the same lock; which in your case obviously does not happen; as such no guarantees are in place. In general happens-before guarantees (that you would require here) only work for pairs, for both reader and writer.

Race condition in Resource ID based locking manager

I'm trying to write a locking manager that will be called from multiple threads. This manager handles locking based on various resource IDs. These can vary very much, so keeping a lock in memory for each one would probably cause large memory usage. This is why after a lock is no longer used (the number of threads using it reaches 0), it is removed from memory.
It can exclusively lock threads based on the requested resource ID (if two threads lock the same ID, one will wait for the other to unlock it), or completely exclude all other threads using a ReentrantReadWriteLock.
I am experiencing a race condition where a lock is removed from memory when unlocked by the last thread that holds it, but other threads still try to unlock it? This results in a NPE which I cannot explain.
I have tried using AtomicInteger instead of the current volatile variable, thinking it may have something to do with that but it had similar results.
Here is the problematic class:
/**
* This class provides locks for reading and writing, and bulk operations lock on the entire class.
*
* If a bulk operation is not in progress, class based locking is transparent.
* #author KiralyCraft
*
*/
public class ReadWriteHighLevelLocking
{
private class Semaphore
{
private ReentrantLock lock;
private volatile int acquiredLocks;
public Semaphore()
{
this.acquiredLocks = 0;
this.lock = new ReentrantLock();
}
public synchronized int incrementAndGet()
{
return ++acquiredLocks;
}
public synchronized int decrementAndGet()
{
return --acquiredLocks;
}
}
private ReentrantReadWriteLock classBasedLock;
private volatile HashMap<String, Semaphore> stateChangeLocks;
public ReadWriteHighLevelLocking()
{
this.stateChangeLocks = new HashMap<String,Semaphore>();
this.classBasedLock = new ReentrantReadWriteLock();
}
/**
* Acquires a lock for the specified resource ID.
*
* May block if another thread is currently holding a bulk lock.
* #param resourceID
*/
public void acquireLock(String resourceID)
{
classBasedLock.readLock().lock(); //Using it reversed. There can be any number of operations (using the read lock), but only one bulk operation (sacrifice)
Semaphore stateChangeLock;
synchronized(stateChangeLocks)
{
if ((stateChangeLock = stateChangeLocks.get(resourceID))==null)
{
stateChangeLocks.put(resourceID, (stateChangeLock = new Semaphore()));
}
}
stateChangeLock.lock.lock();
stateChangeLock.incrementAndGet();
}
public void releaseLock(String resourceID)
{
Semaphore stateChangeLock;
synchronized(stateChangeLocks)
{
stateChangeLock = stateChangeLocks.get(resourceID);
if (stateChangeLock.decrementAndGet() == 0) //<----------------- HERE IS THE NPE
{
stateChangeLocks.remove(resourceID);
}
}
stateChangeLock.lock.unlock();
classBasedLock.readLock().unlock();
}
/**
* When a bulk lock is acquired, all other operations are delayed until this one is released.
*/
public void acquireBulkLock()
{
classBasedLock.writeLock().lock(); //Using it reversed. There can be any number of writers (using the read lock), but only one reader (sacrifice)
}
public void releaseBulkLock()
{
classBasedLock.writeLock().unlock();
}
}
Sample caller class:
public abstract class AbstractDatabaseLockingController
{
...
private ReadWriteHighLevelLocking highLevelLock;
public AbstractDatabaseLockingController(DatabaseInterface db)
{
this.db = db;
this.highLevelLock = new ReadWriteHighLevelLocking();
}
...
public <T extends DatabaseIdentifiable> boolean executeSynchronizedUpdate(T theEntity,AbstractSynchronousOperation<T> aso)
{
boolean toReturn;
String lockID = theEntity.getId()+theEntity.getClass().getSimpleName();
highLevelLock.acquireLock(lockID);
toReturn = aso.execute(theEntity,db);
highLevelLock.releaseLock(lockID);
return toReturn;
}
...
public <T extends DatabaseIdentifiable> List<T> executeSynchronizedGetAllWhereFetch(Class<T> objectType, DatabaseQuerySupplier<T> dqs)
{
List<T> toReturn;
highLevelLock.acquireBulkLock();
toReturn = db.getAllWhere(objectType, dqs);
highLevelLock.releaseBulkLock();
return toReturn;
}
}
NOTE: All places where such a locking manager is used follow the acquire/release pattern from the sample class. It's basically the only place where it is used. Other threads may call the above methods indirectly through the sample class's children
I seem to have fixed the issue by updating the following code:
synchronized(stateChangeLocks)
{
if ((stateChangeLock = stateChangeLocks.get(resourceID))==null)
{
stateChangeLocks.put(resourceID, (stateChangeLock = new Semaphore()));
}
}
stateChangeLock.lock.lock();
stateChangeLock.incrementAndGet();
to
synchronized(stateChangeLocks)
{
if ((stateChangeLock = stateChangeLocks.get(resourceID))==null)
{
stateChangeLocks.put(resourceID, (stateChangeLock = new Semaphore()));
}
stateChangeLock.incrementAndGet();
}
stateChangeLock.lock.lock();

Can I synchronize method by parameter

Can I synchronize method by parameter?
For example - I get person to some method and I want to do some operation for person, but if few thread call this method for the same person I want to do it one by one.
private void dosomething(Long id, Person person) {
dosomethingelse(id, person);
}
How to call dosomethingelse (id, person) only for the same id one by one? but I want that this code for different id-s can be called multithreadly
I wrote this code, but maybe something wrong here or something can be better.
public static class LatchByValue <T> {
public void latch(T value, ConsumerWithException<T> consummer) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
try {
CountDownLatch previousLatch = null;
// we are checking if another thread is already calling this method with the same id
// if sync has CountDownLatch so another thread is already calling this method
// or we put our latch and go on
while ((previousLatch = sync.putIfAbsent(value, latch)) != null) {
try {
// we are waiting for another thread, we are waiting for all threads that put their latch before our thread
previousLatch.await();
} catch (InterruptedException e) {
return;
}
}
consummer.accept(value);
} finally {
latch.countDown();
sync.remove(value, latch);
}
}
private ConcurrentHashMap<T, CountDownLatch> sync = new ConcurrentHashMap<>();
}
Example:
LatchByValue<Long> latch = new LatchByValue<>();
private void dosomething(Long id, Person person) {
latch.latch(
id,
currentId -> { dosomethingelse(currentId, person); }
);
}
Problem with using a CountdownLatch is that you can't "increment" the count so you need to replace the existing latch when it's been used, which complicates the code.
You could instead use a Semaphore with one permit which would allow you to do the same thing but in a simpler way.
Semaphore s = sync.computeIfAbsent(value, x -> new Semaphore(1, true));
s.acquire(); //this blocks and throws InterruptedException, which you need to handle
try {
consummer.accept(value);
} finally {
s.release();
}
You can use synchronized keyword on the parameter passed (culprit: it cannot be null!). And that also allows you to stop worrying about re-acquiring the lock (it's reentrant).
So the implementation would look like:
private void doSomething(Long id, Person person) {
synchronized (person) {
// do something
}
}
Remember that any other accesses (not in doSomething call) also would need to have the synchronization block, e.g.:
// another method, unrelated, but does something with 'person'
private void doSomethingElse(Person person, ... /* other arguments */) {
synchronized (person) {
// do something
}
}
It would be good document (in Person's javadoc) that the user needs to acquire the lock for that object.
If you want to provide a critical section for <id, person> tuple, you'd need to change your API a bit - and then pass that object around in your application.
private void doSomething(IdAndPerson idAndPerson) {
synchronized (idAndPerson) {
// do something
}
}
class IdAndPerson {
private final Long id;
private final Person person;
// constructor etc.
}
private static final Set<Long> lockedIds = new HashSet<>();
private void lock(Long id) throws InterruptedException {
synchronized (lockedIds) {
while (!lockedIds.add(id)) {
lockedIds.wait();
}
}
}
private void unlock(Long id) {
synchronized (lockedIds) {
lockedIds.remove(id);
lockedIds.notifyAll();
}
}
public void doSomething(Long id) throws InterruptedException {
try {
lock(id);
//Put your code here.
//For different ids it is executed in parallel.
//For equal ids it is executed synchronously.
} finally {
unlock(id);
}
}
id can be not only an 'Long' but any class with correctly overridden 'equals' and 'hashCode' methods.
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.

Thread-safe multiton pattern

Inspired by a comment to an given answer I tried to create a thread-safe implementation of the multiton pattern, which relies on unique keys and performs locks on them (I have the idea from JB Nizet's answer on this question).
Question
Is the implementation I provided viable?
I'm not interested in whether Multiton (or Singleton) are in general good patterns, it would result in a discussion. I just want a clean and working implementation.
Contras:
You have to know how many instances you want to create at compile time .
Pros
No lock on whole class, or whole map. Concurrent calls to getInstanceare possible.
Getting instances via key object, and not just unbounded int or String, so you can be sure to get an non-null instance after the method call.
Thread-safe (at least that's my impression).
public class Multiton
{
private static final Map<Enum<?>, Multiton> instances = new HashMap<Enum<?>, Multiton>();
private Multiton() {System.out.println("Created instance."); }
/* Can be called concurrently, since it only synchronizes on id */
public static <KEY extends Enum<?> & MultitionKey> Multiton getInstance(KEY id)
{
synchronized (id)
{
if (instances.get(id) == null)
instances.put(id, new Multiton());
}
System.out.println("Retrieved instance.");
return instances.get(id);
}
public interface MultitionKey { /* */ }
public static void main(String[] args) throws InterruptedException
{
//getInstance(Keys.KEY_1);
getInstance(OtherKeys.KEY_A);
Runnable r = new Runnable() {
#Override
public void run() { getInstance(Keys.KEY_1); }
};
int size = 100;
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < size; i++)
threads.add(new Thread(r));
for (Thread t : threads)
t.start();
for (Thread t : threads)
t.join();
}
enum Keys implements MultitionKey
{
KEY_1;
/* define more keys */
}
enum OtherKeys implements MultitionKey
{
KEY_A;
/* define more keys */
}
}
I tried to prevent the resizing of the map and the misuse of the enums I sychronize on.
It's more of a proof of concept, before I can get it over with! :)
public class Multiton
{
private static final Map<MultitionKey, Multiton> instances = new HashMap<MultitionKey, Multiton>((int) (Key.values().length/0.75f) + 1);
private static final Map<Key, MultitionKey> keyMap;
static
{
Map<Key, MultitionKey> map = new HashMap<Key, MultitionKey>();
map.put(Key.KEY_1, Keys.KEY_1);
map.put(Key.KEY_2, OtherKeys.KEY_A);
keyMap = Collections.unmodifiableMap(map);
}
public enum Key {
KEY_1, KEY_2;
}
private Multiton() {System.out.println("Created instance."); }
/* Can be called concurrently, since it only synchronizes on KEY */
public static <KEY extends Enum<?> & MultitionKey> Multiton getInstance(Key id)
{
#SuppressWarnings ("unchecked")
KEY key = (KEY) keyMap.get(id);
synchronized (keyMap.get(id))
{
if (instances.get(key) == null)
instances.put(key, new Multiton());
}
System.out.println("Retrieved instance.");
return instances.get(key);
}
private interface MultitionKey { /* */ }
private enum Keys implements MultitionKey
{
KEY_1;
/* define more keys */
}
private enum OtherKeys implements MultitionKey
{
KEY_A;
/* define more keys */
}
}
It is absolutely not thread-safe. Here is a simple example of the many, many things that could go wrong.
Thread A is trying to put at key id1. Thread B is resizing the buckets table due to a put at id2. Because these have different synchronization monitors, they're off to the races in parallel.
Thread A Thread B
-------- --------
b = key.hash % map.buckets.size
copy map.buckets reference to local var
set map.buckets = new Bucket[newSize]
insert keys from old buckets into new buckets
insert into map.buckets[b]
In this example, let's say Thread A saw the map.buckets = new Bucket[newSize] modification. It's not guaranteed to (since there's no happens-before edge), but it may. In that case, it'll be inserting the (key, value) pair into the wrong bucket. Nobody will ever find it.
As a slight variant, if Thread A copied the map.buckets reference to a local var and did all its work on that, then it'd be inserting into the right bucket, but the wrong buckets table; it wouldn't be inserting into the new one that Thread B is about to install as the table for everyone to see. If the next operation on key 1 happens to see the new table (again, not guaranteed to but it may), then it won't see Thread A's actions because they were done on a long-forgotten buckets array.
I'd say not viable.
Synchronizing on the id parameter is fraught with dangers - what if they use this enum for another synchronization mechanism? And of course HashMap is not concurrent as the comments have pointed out.
To demonstrate - try this:
Runnable r = new Runnable() {
#Override
public void run() {
// Added to demonstrate the problem.
synchronized(Keys.KEY_1) {
getInstance(Keys.KEY_1);
}
}
};
Here's an implementation that uses atomics instead of synchronization and therefore should be more efficient. It is much more complicated than yours but handling all of the edge cases in a Miltiton IS complicated.
public class Multiton {
// The static instances.
private static final AtomicReferenceArray<Multiton> instances = new AtomicReferenceArray<>(1000);
// Ready for use - set to false while initialising.
private final AtomicBoolean ready = new AtomicBoolean();
// Everyone who is waiting for me to initialise.
private final Queue<Thread> waiters = new ConcurrentLinkedQueue<>();
// For logging (and a bit of linguistic fun).
private final int forInstance;
// We need a simple constructor.
private Multiton(int forInstance) {
this.forInstance = forInstance;
log(forInstance, "New");
}
// The expensive initialiser.
public void init() throws InterruptedException {
log(forInstance, "Init");
// ... presumably heavy stuff.
Thread.sleep(1000);
// We are now ready.
ready();
}
private void ready() {
log(forInstance, "Ready");
// I am now ready.
ready.getAndSet(true);
// Unpark everyone waiting for me.
for (Thread t : waiters) {
LockSupport.unpark(t);
}
}
// Get the instance for that one.
public static Multiton getInstance(int which) throws InterruptedException {
// One there already?
Multiton it = instances.get(which);
if (it == null) {
// Lazy make.
Multiton newIt = new Multiton(which);
// Successful put?
if (instances.compareAndSet(which, null, newIt)) {
// Yes!
it = newIt;
// Initialise it.
it.init();
} else {
// One appeared as if by magic (another thread got there first).
it = instances.get(which);
// Wait for it to finish initialisation.
// Put me in its queue of waiters.
it.waiters.add(Thread.currentThread());
log(which, "Parking");
while (!it.ready.get()) {
// Park me.
LockSupport.park();
}
// I'm not waiting any more.
it.waiters.remove(Thread.currentThread());
log(which, "Unparked");
}
}
return it;
}
// Some simple logging.
static void log(int which, String s) {
log(new Date(), "Thread " + Thread.currentThread().getId() + " for Multiton " + which + " " + s);
}
static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
// synchronized so I don't need to make the DateFormat ThreadLocal.
static synchronized void log(Date d, String s) {
System.out.println(dateFormat.format(d) + " " + s);
}
// The tester class.
static class MultitonTester implements Runnable {
int which;
private MultitonTester(int which) {
this.which = which;
}
#Override
public void run() {
try {
Multiton.log(which, "Waiting");
Multiton m = Multiton.getInstance(which);
Multiton.log(which, "Got");
} catch (InterruptedException ex) {
Multiton.log(which, "Interrupted");
}
}
}
public static void main(String[] args) throws InterruptedException {
int testers = 50;
int multitons = 50;
// Do a number of them. Makes n testers for each Multiton.
for (int i = 1; i < testers * multitons; i++) {
// Which one to create.
int which = i / testers;
//System.out.println("Requesting Multiton " + i);
new Thread(new MultitonTester(which+1)).start();
}
}
}
I'm not a Java programmer, but: HashMap is not safe for concurrent access. Might I recommend ConcurrentHashMap.
private static final ConcurrentHashMap<Object, Multiton> instances = new ConcurrentHashMap<Object, Multiton>();
public static <TYPE extends Object, KEY extends Enum<Keys> & MultitionKey<TYPE>> Multiton getInstance(KEY id)
{
Multiton result;
synchronized (id)
{
result = instances.get(id);
if(result == null)
{
result = new Multiton();
instances.put(id, result);
}
}
System.out.println("Retrieved instance.");
return result;
}

Categories

Resources