package com.compomics.util.maps;
import java.util.HashMap;
import java.util.HashSet;
import java.util.concurrent.Semaphore;
/**
* Map of semaphores acting like a map mutex to block some parts of the maps.
*
* @author Marc Vaudel
*
* @param <K> the type of keys to use
*/
public class MapMutex<K> {
/**
* The maximal number of keys to keep in the map.
*/
private Integer cacheLimitSize = null;
/**
* The number of permits per key.
*/
public final int permits;
/**
* Map of semaphores used to control the threads.
*/
private final HashMap<K, Semaphore> semaphoreMap;
/**
* Mutex for the edition of the semaphore map.
*/
private Semaphore mutex = new Semaphore(1, true);
/**
* Boolean indicating whether the map is being edited.
*/
private boolean writing = false;
/**
* Constructor.
*
* @param permits the number of permits per key, 1 if null
* @param cacheLimitSize the size limit where semaphores will be removed
* upon release of all permits, ignored if null
* @param initialSize the initial size of the map, the default HashMap size
* if null
*/
public MapMutex(Integer permits, Integer cacheLimitSize, Integer initialSize) {
if (permits != null) {
this.permits = permits;
} else {
this.permits = 1;
}
if (cacheLimitSize != null) {
this.cacheLimitSize = cacheLimitSize;
}
if (initialSize != null) {
this.semaphoreMap = new HashMap<K, Semaphore>(initialSize);
} else {
this.semaphoreMap = new HashMap<K, Semaphore>();
}
}
/**
* Constructor with one permit per key and not cache limit size.
*/
public MapMutex() {
this(null, null, null);
}
/**
* Constructor with no cache limit size.
*
* @param permits the number of permits per key
*/
public MapMutex(int permits) {
this(permits, null, null);
}
/**
* Acquires a permit for the given key.
*
* @param key the key
*
* @throws InterruptedException exception thrown if the thread is
* interrupted
*/
public void acquire(K key) throws InterruptedException {
// Block if the map is being edited
if (writing) {
mutex.acquire();
mutex.release();
}
// Get semaphore from map
Semaphore semaphore = semaphoreMap.get(key);
// Add semaphore to map if none is present and acquire
if (semaphore == null) {
mutex.acquire();
semaphore = semaphoreMap.get(key);
if (semaphore == null) {
writing = true;
semaphore = new Semaphore(permits);
semaphoreMap.put(key, semaphore);
writing = false;
}
mutex.release();
}
semaphore.acquire();
}
/**
* Releases the permit for the given key. If the size of the map exceeds the
* cache size semaphores with all available permits will be removed from the
* map. Despite my best efforts no guarantee that it does not overwrite an
* acquire.
*
* @param key the key to release
*
* @throws InterruptedException exception thrown if the thread is
* interrupted
*/
public void release(K key) throws InterruptedException {
// Block if the map is being edited
if (writing) {
mutex.acquire();
mutex.release();
}
// Edit map if over max size
if (cacheLimitSize != null && semaphoreMap.size() > cacheLimitSize) {
writing = true;
mutex.acquire();
writing = true;
HashSet<K> keys = new HashSet<K>(semaphoreMap.keySet());
for (K tempKey : keys) {
Semaphore tempSemaphore = semaphoreMap.get(tempKey);
if (!tempSemaphore.hasQueuedThreads() && tempSemaphore.availablePermits() == permits) {
semaphoreMap.remove(tempKey);
}
}
writing = false;
mutex.release();
}
// Get semaphore from map
Semaphore semaphore = semaphoreMap.get(key);
// Release
semaphore.release();
}
}