Extend HashMapLOW interface. Put PlayerData FCFS under lock.

HashMapLOW
* Add a constructor for using an external lock.
* Add putIfAbsent.

DataManager
* Use playerDataMap.putIfAbsent, return the PlayerData instance that has
been there first.
This commit is contained in:
asofold 2017-04-13 12:02:32 +02:00
parent c53d3f1521
commit 8077b4a0dc
3 changed files with 63 additions and 8 deletions

View File

@ -66,14 +66,17 @@ public class HashMapLOW <K, V> {
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
final V oldValue = this.value;
this.value = value;
@ -140,11 +143,15 @@ public class HashMapLOW <K, V> {
/**
* Called under lock.
*
* @param key
* @param value
* @param ifAbsent
* If true, an existing non-null (!) value will not be
* overridden.
* @return
*/
V put(final int hashCode, final K key, final V value) {
V put(final int hashCode, final K key, final V value, final boolean ifAbsent) {
int emptyIndex;
if (size == 0) {
emptyIndex = 0;
@ -173,7 +180,9 @@ public class HashMapLOW <K, V> {
}
if (oldEntry != null) {
final V oldValue = oldEntry.getValue();
oldEntry.setValue(value);
if (oldValue == null || !ifAbsent) {
oldEntry.setValue(value);
}
return oldValue;
}
}
@ -405,7 +414,7 @@ public class HashMapLOW <K, V> {
// Instance members
///////////////////////
private final Lock lock = new ReentrantLock();
private final Lock lock;
/** Intended/expected size. */
private final int targetSize;
@ -420,11 +429,21 @@ public class HashMapLOW <K, V> {
// TODO: Configurable: allow shrink.
/**
*
* Initialize with a ReentrantLock.
* @param targetSize
* Expected (average) number of elements in the map.
*/
public HashMapLOW(int targetSize) {
this(new ReentrantLock(), targetSize);
}
/**
* Initialize with a certain lock.
* @param lock
* @param targetSize
*/
public HashMapLOW(Lock lock, int targetSize) {
this.lock = lock;
this.targetSize = targetSize;
buckets = newBuckets(targetSize);
}
@ -502,6 +521,31 @@ public class HashMapLOW <K, V> {
* @return
*/
public V put(final K key, final V value) {
return put(key, value, false);
}
/**
* Immediate put, only if there is no value or a null value set for the key,
* under lock.
*
* @param key
* @param value
* @return
*/
public V putIfAbsent(final K key, final V value) {
return put(key, value, true);
}
/**
*
* @param key
* @param value
* @param ifAbsent
* If true, an existing non-null (!) value will not be
* overridden.
* @return
*/
private final V put(final K key, final V value, final boolean ifAbsent) {
final int hashCode = getHashCode(key);
lock.lock();
final int index = getBucketIndex(hashCode, buckets.length);
@ -510,7 +554,7 @@ public class HashMapLOW <K, V> {
bucket = new LHMBucket<K, V>();
buckets[index] = bucket;
}
final V oldValue = bucket.put(hashCode, key, value);
V oldValue = bucket.put(hashCode, key, value, ifAbsent);
if (oldValue == null) {
size ++;
if (size > (int) (loadFactor * (float) buckets.length)) {

View File

@ -694,9 +694,10 @@ public class DataManager implements Listener, INeedConfig, ComponentRegistry<IRe
return null;
}
else {
// Creating this should be mostly harmless.
final PlayerData newData = new PlayerData(playerId, playerName);
instance.playerData.put(playerId, newData);
return newData;
final PlayerData oldData = instance.playerData.putIfAbsent(playerId, newData);
return oldData == null ? newData : oldData;
}
}

View File

@ -25,8 +25,18 @@ import fr.neatmonster.nocheatplus.components.data.IData;
import fr.neatmonster.nocheatplus.utilities.TickTask;
/**
* Central player data object.
* Central player-specific data object.
* <ul>
* <li>Access to on-tick request functionality.</li>
* <li>TBD: Permission cache.</li>
* <li>TBD: Check data.</li>
* <li>TBD: Exemptions</li>
* <li>...</li>
* </ul>
* <hr>
* Creating PlayerData must always be thread-safe and fail-safe.
* <hr>
* OLD javadocs to be cleaned up (...):<br>
* On the medium run this is intended to carry all data for the player...
* <li>Checks data objects.</li>
* <li>Time stamps for logged out players</li>