Is it a thread-safe mechanism? - java

Is this class thread-safe?
class Counter {
private ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
if (this.map.get(name) == null) {
this.map.putIfAbsent(name, new AtomicLong());
}
return this.map.get(name).incrementAndGet();
}
}
What do you think?

Yes, provided you make the map final. The if is not necessary but you can keep it for performance reasons if you want, although it will most likely not make a noticeable difference:
public long add(String name) {
this.map.putIfAbsent(name, new AtomicLong());
return this.map.get(name).incrementAndGet();
}
EDIT
For the sake of it, I have quickly tested both implementation (with and without the check). 10 millions calls on the same string take:
250 ms with the check
480 ms without the check
Which confirms what I said: unless you call this method millions of time or it is in performance critical part of your code, it does not make a difference.
EDIT 2
Full test result - see the BetterCounter which yields even better results. Now the test is very specific (no contention + the get always works) and does not necessarily correspond to your usage.
Counter: 482 ms
LazyCounter: 207 ms
MPCounter: 303 ms
BetterCounter: 135 ms
public class Test {
public static void main(String args[]) throws IOException {
Counter count = new Counter();
LazyCounter lazyCount = new LazyCounter();
MPCounter mpCount = new MPCounter();
BetterCounter betterCount = new BetterCounter();
//WARM UP
for (int i = 0; i < 10_000_000; i++) {
count.add("abc");
lazyCount.add("abc");
mpCount.add("abc");
betterCount.add("abc");
}
//TEST
long start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
count.add("abc");
}
long end = System.nanoTime();
System.out.println((end - start) / 1000000);
start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
lazyCount.add("abc");
}
end = System.nanoTime();
System.out.println((end - start) / 1000000);
start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
mpCount.add("abc");
}
end = System.nanoTime();
System.out.println((end - start) / 1000000);
start = System.nanoTime();
for (int i = 0; i < 10_000_000; i++) {
betterCount.add("abc");
}
end = System.nanoTime();
System.out.println((end - start) / 1000000);
}
static class Counter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
this.map.putIfAbsent(name, new AtomicLong());
return this.map.get(name).incrementAndGet();
}
}
static class LazyCounter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
if (this.map.get(name) == null) {
this.map.putIfAbsent(name, new AtomicLong());
}
return this.map.get(name).incrementAndGet();
}
}
static class BetterCounter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
AtomicLong counter = this.map.get(name);
if (counter != null)
return counter.incrementAndGet();
AtomicLong newCounter = new AtomicLong();
counter = this.map.putIfAbsent(name, newCounter);
return (counter == null ? newCounter.incrementAndGet() : counter.incrementAndGet());
}
}
static class MPCounter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
final AtomicLong newVal = new AtomicLong(),
prevVal = map.putIfAbsent(name, newVal);
return (prevVal != null ? prevVal : newVal).incrementAndGet();
}
}
}

EDIT
Yes if you make the map final. Otherwise, it's not guaranteed that all threads see the most recent version of the map data structure when they call add() for the first time.
Several threads can reach the body of the if(). The putIfAbsent() will make sure that only a single AtomicLong is put into the map.
There should be no way that putIfAbsent() can return without the new value being in the map.
So when the second get() is executed, it will never get a null value and since only a single AtomicLong can have been added to the map, all threads will get the same instance.
[EDIT2] The next question: How efficient is this?
This code is faster since it avoids unnecessary searches:
public long add(String name) {
AtomicLong counter = map.get( name );
if( null == counter ) {
map.putIfAbsent( name, new AtomicLong() );
counter = map.get( name ); // Have to get again!!!
}
return counter.incrementAndGet();
}
This is why I prefer Google's CacheBuilder which has a method that is called when a key can't be found. That way, the map is searched only once and I don't have to create extra instances.

No one seems to have the complete solution, which is:
public long add(String name) {
AtomicLong counter = this.map.get(name);
if (counter == null) {
AtomicLong newCounter = new AtomicLong();
counter = this.map.putIfAbsent(name, newCounter);
if(counter == null) {
counter = newCounter;
}
}
return counter.incrementAndGet();
}

What about this:
class Counter {
private final ConcurrentMap<String, AtomicLong> map =
new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
this.map.putIfAbsent(name, new AtomicLong());
return this.map.get(name).incrementAndGet();
}
}
The map should be final to guarantee it is fully visible to all threads before the first method is invoked. (see 17.5 final Field Semantics (Java Language Specification) for details)
I think the if is redundant, I hope I'm not overseeing anything.
Edit: Added a quote from the Java Language Specification:

This solution (note that I am showing only the body of the add method -- the rest stays the same!) spares you of any calls to get:
final AtomicLong newVal = new AtomicLong(),
prevVal = map.putIfAbsent(name, newVal);
return (prevVal != null? prevVal : newVal).incrementAndGet();
In all probability an extra get is much costlier than an extra new AtomicLong().

I think you would be better off with something like this:
class Counter {
private ConcurrentMap<String, AtomicLong> map = new ConcurrentHashMap<String, AtomicLong>();
public long add(String name) {
AtomicLong counter = this.map.get(name);
if (counter == null) {
AtomicLong newCounter = new AtomicLong();
counter = this.map.putIfAbsent(name, newCounter);
if (counter == null) {
// The new counter was added - use it
counter = newCounter;
}
}
return counter.incrementAndGet();
}
}
Otherwise multiple threads may add simultaneously and you wouldn't notice (since you ignore the value returned by putIfAbsent).
I assume that you never recreate the map.

Related

Immutable 100%, but still not thread-safe

I've read a lot about thread-safety. In certain part of my multi-threaded program, I preferred to try the immutability. After getting incorrect results, I noticed my immutable object is not thread-safe although it is 100% immutable. Please correct me if I'm wrong.
public final class ImmutableGaugeV4 {
private final long max, current;
public ImmutableGaugeV4(final long max) {
this(max, 0);
}
private ImmutableGaugeV4(final long max, final long current) {
this.max = max;
this.current = current;
}
public final ImmutableGaugeV4 increase(final long increment) {
final long c = current;
return new ImmutableGaugeV4(max, c + increment);
}
public final long getCurrent() {
return current;
}
public final long getPerc() {
return current * 100 / max;
}
#Override
public final String toString() {
return "ImmutableGaugeV4 [max=" + max + ", current=" + current + "](" + getPerc() + "%)";
}
}
aaaaa
public class T4 {
public static void main(String[] args) {
new T4().x();
}
ImmutableGaugeV4 g3 = new ImmutableGaugeV4(10000);
private void x() {
for (int i = 0; i < 10; i++) {
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++) {
g3 = g3.increase(1);
System.out.println(g3);
}
}
}.start();
}
}
}
Sometimes I'm getting correct results, and most of the times I'm not
ImmutableGaugeV4 [max=10000, current=9994](99%)
ImmutableGaugeV4 [max=10000, current=9995](99%)
ImmutableGaugeV4 [max=10000, current=9996](99%)
ImmutableGaugeV4 [max=10000, current=9997](99%)
What is wrong with this immutable object? What is missing to make it thread-safe without using intrinsic locks?
Neither
final long c = current;
return new ImmutableGaugeV4(max, c + increment);
nor
g3 = g3.increase(1);
is thread-safe. These compound actions aren't atomic.
I recommend reading "Java concurrency in practice" by Brian Goetz: the chapters devoted to compound actions and "publication and escape" problems.
Your problem is that you are not using thread safe operations for your numeric variables max and current. Because of that, many threads can get the same value from them even tough it has already been changed.
You could add synchronized blocks to handle reading / writing to them, but the best approach is to use thread safe classes to handle that for you.
If you need long values, that would be AtomicLong. Take a look at it’s documentation, it has methods to do the operations you want.
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicLong.html
Whenever you’re multithreading you should go for threadsafe objects, such as the Atomic family, ConcurrentHashMap for maps, and so on.
Hope it helps!
The only problem here is the following line:
g3 = g3.increase(1);
This is equivalent to the following lines:
var tmp = g3;
tmp = tmp.increase(1);
g3 = tmp;
To fix this, you could use a Compare And Swap:
private static final VarHandle G3;
static {
try {
G3 = MethodHandles.lookup().findVarHandle(T4.class, "g3", ImmutableGaugeV4.class);
} catch (ReflectiveOperationException roe) {
throw new Error(roe);
}
}
And then replace g3 = g3.increase(1); with:
ImmutableGaugeV4 oldVal, newVal;
do {
oldVal = g3;
newVal = oldVal.increase(1);
} while (!G3.compareAndSet(T4.this, oldVal, newVal));
System.out.println(newVal);
In the end, your T4 becomes:
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
public class T4 {
public static void main(String[] args) {
new T4().x();
}
ImmutableGaugeV4 g3 = new ImmutableGaugeV4(10000);
private static final VarHandle G3;
static {
try {
G3 = MethodHandles.lookup().findVarHandle(T4.class, "g3", ImmutableGaugeV4.class);
} catch (ReflectiveOperationException roe) {
throw new Error(roe);
}
}
private void x() {
for (int i = 0; i < 10; i++) {
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++) {
ImmutableGaugeV4 oldVal, newVal;
do {
oldVal = g3;
newVal = oldVal.increase(1);
} while (!G3.compareAndSet(T4.this, oldVal, newVal));
System.out.println(newVal);
}
}
}.start();
}
}
}

Finding bestfit with brute-force search

Hello I am relative new in programming and need guidance.
We are on a plantage. I have a number of Fields that contain different amounts of trees. On each field a set of tasks have to be done. The tasks are the same but the time varies since the fields are different sizes. I want to generate a list of tasks that matches the assigned working time for the day best.
I believe this is a Job Shop scheduling problem (NP-hard) but as far as i know it can be solved with brute-force search since the data set is small. How do I generate all combinations within the assigned time and return the best fit? I tried to look at some pseudo code but frankly im quite lost and my attempt is rather poor:
//Brute-force search
// 1. first(P): generate a first candidate solution for P.
// 2. next(P,c): generate the next candidate for P after the current one c.
// 3. valid(P,c): check whether candidate c is a solution for P-
// 4. output(P,c): use the solution c of P as appropriate to the application.
public static ArrayList<Task> generatedList2(int totalTime) {
ArrayList<Task> bestFit = new ArrayList<>();
ArrayList<Task> tmpFit = new ArrayList<>();
int tmpTime = 0;
int bestFitTime = -1;
Task testTask = new Task("TestTask", 0);
bestFit.add(testTask); //1
for(Field f : fields) { //2
for(Task t : f.getUndoneTasks()) {
if(f.getTaskTime(t) < totalTime) {
tmpFit.add(t);
tmpTime += f.getTaskTime(t);
totalTime -= f.getTaskTime(t);
}
}
if(tmpTime < bestFitTime) { //3
bestFit = new ArrayList<Task>(tmpFit);
bestFitTime = tmpTime;
tmpFit.clear();
}
else {
tmpFit.clear();
}
}
return bestFit; //4
}
Updated solution:
public static ArrayList<Task> RecursivelyGetAnswer(ArrayList<Task> listSoFar,
ArrayList<Task> masterList, ArrayList<Task> bestList, int limit, int index) {
for (int i = index; i < masterList.size(); i++) {
Task task = masterList.get(i);
double listSoFarTotal = getTotal(listSoFar) + task.getTaskLength();
if (listSoFarTotal <= limit) {
int bestListTotal = getTotal(bestList);
listSoFar.add(task);
if (listSoFarTotal > bestListTotal) {
bestList = new ArrayList<Task>(listSoFar);
}
else if(100 - ((float) (limit - bestListTotal)/bestListTotal * 100) > 95) {
break;
}
bestList = RecursivelyGetAnswer(listSoFar, masterList, bestList, limit, i+1);
listSoFar.remove(task);
}
}
return bestList;
}
I came up with a recursive solution. For the purposes of my solution, I assumed that you just had a list of tasks, instead of tasks inside fields.
import java.util.ArrayList;
class Task
{
public int taskLength;
public Task(int taskLength)
{
this.taskLength = taskLength;
}
#Override
public String toString()
{
return "T" + taskLength;
}
}
public class Answers
{
public static void main(String args[])
{
ArrayList masterList = new ArrayList();
//Add some sample data
masterList.add(new Task(555));
masterList.add(new Task(1054));
masterList.add(new Task(888));
masterList.add(new Task(5923));
masterList.add(new Task(2342));
masterList.add(new Task(6243));
masterList.add(new Task(9227));
masterList.add(new Task(4111));
masterList.add(new Task(4322));
masterList.add(new Task(782));
final int limit = 9999;
ArrayList<Task> bestList = RecursivelyGetAnswer(new ArrayList<>(), masterList, new ArrayList<>(), limit, 0);
System.out.println(bestList.toString());
System.out.println(getTotal(bestList));
}
public static ArrayList<Task> RecursivelyGetAnswer(ArrayList<Task> listSoFar, ArrayList<Task> masterList, ArrayList<Task> bestList, int limit, int index)
{
for (int i = index; i < masterList.size(); i++)
{
Task task = masterList.get(i);
if (getTotal(listSoFar) + task.taskLength <= limit)
{
listSoFar.add(task);
if (getTotal(listSoFar) > getTotal(bestList))
{
bestList = new ArrayList(listSoFar);
}
bestList = RecursivelyGetAnswer(listSoFar, masterList, bestList, limit, i+1);
listSoFar.remove(task);
}
}
return bestList;
}
// Given a list of tasks, get the sum of the lengths of the tasks.
public static int getTotal(ArrayList<Task> myList)
{
int sum = 0;
for (Task t:myList)
sum += t.taskLength;
return sum;
}
}

Incrementing and removing elements of ConcurrentHashMap

There is class Counter, which contains a set of keys and allows incrementing value of each key and getting all values. So, the task I'm trying to solve is the same as in Atomically incrementing counters stored in ConcurrentHashMap . The difference is that the set of keys is unbounded, so new keys are added frequently.
In order to reduce memory consumption, I clear values after they are read, this happens in Counter.getAndClear(). Keys are also removed, and this seems to break things up.
One thread increments random keys and another thread gets snapshots of all values and clears them.
The code is below:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.Map;
import java.util.HashMap;
import java.lang.Thread;
class HashMapTest {
private final static int hashMapInitSize = 170;
private final static int maxKeys = 100;
private final static int nIterations = 10_000_000;
private final static int sleepMs = 100;
private static class Counter {
private ConcurrentMap<String, Long> map;
public Counter() {
map = new ConcurrentHashMap<String, Long>(hashMapInitSize);
}
public void increment(String key) {
Long value;
do {
value = map.computeIfAbsent(key, k -> 0L);
} while (!map.replace(key, value, value + 1L));
}
public Map<String, Long> getAndClear() {
Map<String, Long> mapCopy = new HashMap<String, Long>();
for (String key : map.keySet()) {
Long removedValue = map.remove(key);
if (removedValue != null)
mapCopy.put(key, removedValue);
}
return mapCopy;
}
}
// The code below is used for testing
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread = new Thread(new Runnable() {
public void run() {
for (int j = 0; j < nIterations; j++) {
int index = ThreadLocalRandom.current().nextInt(maxKeys);
counter.increment(Integer.toString(index));
}
}
}, "incrementThread");
Thread readerThread = new Thread(new Runnable() {
public void run() {
long sum = 0;
boolean isDone = false;
while (!isDone) {
try {
Thread.sleep(sleepMs);
}
catch (InterruptedException e) {
isDone = true;
}
Map<String, Long> map = counter.getAndClear();
for (Map.Entry<String, Long> entry : map.entrySet()) {
Long value = entry.getValue();
sum += value;
}
System.out.println("mapSize: " + map.size());
}
System.out.println("sum: " + sum);
System.out.println("expected: " + nIterations);
}
}, "readerThread");
thread.start();
readerThread.start();
thread.join();
readerThread.interrupt();
readerThread.join();
// Ensure that counter is empty
System.out.println("elements left in map: " + counter.getAndClear().size());
}
}
While testing I have noticed that some increments are lost. I get the following results:
sum: 9993354
expected: 10000000
elements left in map: 0
If you can't reproduce this error (that sum is less than expected), you can try to increase maxKeys a few orders of magnitude or decrease hashMapInitSize or increase nIterations (the latter also increases run time). I have also included testing code (main method) in the case it has any errors.
I suspect that the error is happening when capacity of ConcurrentHashMap is increased during runtime. On my computer the code appears to work correctly when hashMapInitSize is 170, but fails when hashMapInitSize is 171. I believe that size of 171 triggers increasing of capacity (128 / 0.75 == 170.66, where 0.75 is the default load factor of hash map).
So, the question is: am I using remove, replace and computeIfAbsent operations correctly? I assume that they are atomic operations on ConcurrentHashMap based on answers to Use of ConcurrentHashMap eliminates data-visibility troubles?. If so, why are some increments lost?
EDIT:
I think that I missed an important detail here that increment() is supposed to be called much more frequently than getAndClear(), so that I try to avoid any explicit locking in increment(). However, I'm going to test performance of different versions later to see if it is really an issue.
I gues the problem is the use of remove while iterating over the keySet. This is what the JavaDoc says for Map#keySet() (my emphasis):
Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined.
The JavaDoc for ConcurrentHashMap give further clues:
Similarly, Iterators, Spliterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration.
The conclusion is that mutating the map while iterating over the keys is not predicatble.
One solution is to create a new map for the getAndClear() operation and just return the old map. The switch has to be protected, and in the example below I used a ReentrantReadWriteLock:
class HashMapTest {
private final static int hashMapInitSize = 170;
private final static int maxKeys = 100;
private final static int nIterations = 10_000_000;
private final static int sleepMs = 100;
private static class Counter {
private ConcurrentMap<String, Long> map;
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReadLock readLock = lock.readLock();
WriteLock writeLock = lock.writeLock();
public Counter() {
map = new ConcurrentHashMap<>(hashMapInitSize);
}
public void increment(String key) {
readLock.lock();
try {
map.merge(key, 1L, Long::sum);
} finally {
readLock.unlock();
}
}
public Map<String, Long> getAndClear() {
ConcurrentMap<String, Long> oldMap;
writeLock.lock();
try {
oldMap = map;
map = new ConcurrentHashMap<>(hashMapInitSize);
} finally {
writeLock.unlock();
}
return oldMap;
}
}
// The code below is used for testing
public static void main(String[] args) throws InterruptedException {
final AtomicBoolean ready = new AtomicBoolean(false);
Counter counter = new Counter();
Thread thread = new Thread(new Runnable() {
public void run() {
for (int j = 0; j < nIterations; j++) {
int index = ThreadLocalRandom.current().nextInt(maxKeys);
counter.increment(Integer.toString(index));
}
}
}, "incrementThread");
Thread readerThread = new Thread(new Runnable() {
public void run() {
long sum = 0;
while (!ready.get()) {
try {
Thread.sleep(sleepMs);
} catch (InterruptedException e) {
//
}
Map<String, Long> map = counter.getAndClear();
for (Map.Entry<String, Long> entry : map.entrySet()) {
Long value = entry.getValue();
sum += value;
}
System.out.println("mapSize: " + map.size());
}
System.out.println("sum: " + sum);
System.out.println("expected: " + nIterations);
}
}, "readerThread");
thread.start();
readerThread.start();
thread.join();
ready.set(true);
readerThread.join();
// Ensure that counter is empty
System.out.println("elements left in map: " + counter.getAndClear().size());
}
}

ConcurrentHashMap vs ConcurrentSkipListMap

I want to compare performance between ConcurrentHashMap and ConcurrentSkipListMap. It's for studying purpose. Of corse the result depends on platform to platform. On my computer expectedly the reading test ConcurrentHashMap more productive then ConcurrentSkipListMap. But the writing test showed more performance ConcurrentSkipListMap. ConcurrentHashMap relies on a hash table, I think it should be more faster. Why is it happen?
package Concurrency;
import java.util.*;
import java.util.concurrent.*;
abstract class Task implements Callable<Long> {
protected static Map<Integer, String> map;
protected int nIteration;
protected static int index;
protected long startTime, endTime;
private static Random random = new Random();
private static char[] chars = "abcdefghijklmnopqrstuvwxyz".toCharArray();
public Task(Map<Integer, String> map, int nIteration) {
Task.map = map;
this.nIteration = nIteration;
}
protected static synchronized String getNextString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
char c = chars[random.nextInt(chars.length)];
sb.append(c);
}
sb.append(index);
return sb.toString();
}
protected static synchronized int getNextInt() { return index++; }
protected static synchronized int getPreviousInt() { return index--; }
protected static synchronized int getCurrentInt() { return index; } // It's for test purpose.
public abstract Long call();
}
class WriterTask extends Task {
public WriterTask(Map<Integer, String> map, int nIteration) { super(map, nIteration); }
public Long call() {
startTime = System.currentTimeMillis();
while(nIteration-- > 0) {
map.put(getNextInt(), getNextString());
}
endTime = System.currentTimeMillis();
return (endTime - startTime);
}
}
class ReaderTask extends Task {
public ReaderTask(Map<Integer,String> map, int nIteration) { super(map, nIteration); }
#Override
public Long call() {
startTime = System.currentTimeMillis();
while(nIteration-- > 0) {
map.remove(getPreviousInt());
}
endTime = System.currentTimeMillis();
return (endTime - startTime);
}
}
public class FourtyThree {
private static List<Future<Long>> result = new LinkedList<>();
private static Map<Integer, String> map;
//private static String mapName;
private static Map<String, Double> makeReport(
int nCycle, int nThreads, boolean isWriter , int nIteration)
throws InterruptedException, ExecutionException {
Long resultTime = 0L;
int numberLine = 0;
double resultAverage;
StringBuilder sb = new StringBuilder();
sb.append(map.getClass().getSimpleName());
sb.append(", Cycle:" + nCycle);
if(isWriter)
sb.append(", Test type:Writing");
else
sb.append(", Test type: Reading");
sb.append(", iteration:" + nIteration);
sb.append(", Threads:" +nThreads);
for(Future<Long> i : result) {
resultTime += i.get();
numberLine++;
}
resultAverage = (double)resultTime / (double)numberLine;
resultAverage = (double)Math.round(resultAverage * 100) / 100;
sb.append(", Average time:" + resultAverage + " milliseconds");
return Collections.singletonMap(sb.toString(), resultAverage);
}
private static void prepareReading(int nIteration) {
ExecutorService exec = Executors.newSingleThreadExecutor();
exec.submit(new WriterTask(map, nIteration));
exec.shutdown();
}
public static Map<String, Double> test( Map<Integer, String> testMap,
int nCycle,
int nThreads,
boolean isWriter ,
int nIteration )
throws InterruptedException, ExecutionException {
map = testMap;
if (!isWriter)
prepareReading(nThreads * nIteration);
ExecutorService exec = Executors.newFixedThreadPool(nThreads);
List<Callable<Long>> tasks = new LinkedList<>();
for(int i = 0; i < nThreads; i++) {
if(isWriter)
tasks.add(new WriterTask(map, nIteration));
else
tasks.add(new ReaderTask(map, nIteration));
}
result = exec.invokeAll(tasks);
exec.shutdown();
map.clear();
return makeReport(nCycle, nThreads, isWriter , nIteration);
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
Map<String, Double> results = new LinkedHashMap<String, Double>();
Collection<Double> resultTime = results.values();
double time = 0;
ConcurrentHashMap<Integer, String> map1 = new ConcurrentHashMap<>();
ConcurrentSkipListMap<Integer, String> map2 = new ConcurrentSkipListMap<>();
for(int i = 0; i < 5; i++) {
results.putAll(test(map1, i, 16, false, 1000));
}
for(Map.Entry<String, Double> entry : results.entrySet()) {
System.out.println(entry.getKey());
time += entry.getValue();
}
time = time / (double)resultTime.size();
time = Math.round(time * 100) / 100;
System.out.print("Average time for all cycles:" + time);
System.out.print(", Max time:" + Collections.max(resultTime));
System.out.print(", Min time:" + Collections.min(resultTime));
}
}
/* Short report:
*** Reading ***
ConcurrentHashMap, Cycle:4, Test type: Reading, iteration:1 000 000, Threads:2
Average time for all cycles:3530.0, Max time:6817.5, Min time:1625.0
ConcurrentSkipListMap, Cycle:4, Test type: Reading, iteration:1 000 000, Threads:2
Average time for all cycles:4716.0, Max time:9337.5, Min time:1294.0
ConcurrentHashMap, Cycle:4, Test type: Reading, iteration:100 000, Threads:16
Average time for all cycles:528.0, Max time:1064.06, Min time:355.25
ConcurrentSkipListMap, Cycle:4, Test type: Reading, iteration:100 000, Threads:16
Average time for all cycles:1081.0, Max time:1732.75, Min time:330.5
*** Writing ***
ConcurrentHashMap, Cycle:4, Test type:Writing, iteration:1 000 000, Threads:2
Average time for all cycles:12112.1, Max time:18261.5, Min time:9111.5
ConcurrentSkipListMap, Cycle:4, Test type:Writing, iteration:1 000 000, Threads:2
Average time for all cycles:11856.7, Max time:18143.0, Min time:8292.0
ConcurrentHashMap, Cycle:4, Test type:Writing, iteration:100 000, Threads:16
Average time for all cycles:9015.0, Max time:16461.75, Min time:5016.5
ConcurrentSkipListMap, Cycle:4, Test type:Writing, iteration:100 000, Threads:16
Average time for all cycles:8922.68, Max time:12383.31, Min time:6783.13
*/

java map concurrent update

I'm trying to create a Map with int values and increase them by multiple threads. two or more threads might increase the same key.
ConcurrentHashMap documentation was very unclear to me since it sais that:
Retrieval operations (including get) generally do not block, so may overlap with update operations (including put and remove)
I wonder if the following code using ConcurrentHashMap will works correctly:
myMap.put(X, myMap.get(X) + 1);
if not, how can I manage such thing?
Concurrent map will not help thread safety of your code. You still can get race condition:
Thread-1: x = 1, get(x)
Thread-2: x = 1, get(x)
Thread-1: put(x + 1) => 2
Thread-2: put(x + 1) => 2
Two increments happened, but you still get only +1. You need a concurrent map only if you aim for modifying the map itself, not its content. Even the simplest HashMap is threadsafe for concurrent reads, given the map is not mutated anymore.
So instead of a threadsafe map for primitive type, you need a threadsafe wrapper for the type. Either something from java.util.concurrent.atomic or roll your own locked container if needing an arbitrary type.
One idea would be combining ConcurrentMap with AtomicInteger, which has a increment method.
AtomicInteger current = map.putIfAbsent(key, new AtomicInteger(1));
int newValue = current == null ? 1 :current.incrementAndGet();
or (more efficiently, thanks #Keppil) with an extra code guard to avoid unnecessary object creation:
AtomicInteger current = map.get(key);
if (current == null){
current = map.putIfAbsent(key, new AtomicInteger(1));
}
int newValue = current == null ? 1 : current.incrementAndGet();
Best practice. You can use HashMap and AtomicInteger.
Test code:
public class HashMapAtomicIntegerTest {
public static final int KEY = 10;
public static void main(String[] args) {
HashMap<Integer, AtomicInteger> concurrentHashMap = new HashMap<Integer, AtomicInteger>();
concurrentHashMap.put(HashMapAtomicIntegerTest.KEY, new AtomicInteger());
List<HashMapAtomicCountThread> threadList = new ArrayList<HashMapAtomicCountThread>();
for (int i = 0; i < 500; i++) {
HashMapAtomicCountThread testThread = new HashMapAtomicCountThread(
concurrentHashMap);
testThread.start();
threadList.add(testThread);
}
int index = 0;
while (true) {
for (int i = index; i < 500; i++) {
HashMapAtomicCountThread testThread = threadList.get(i);
if (testThread.isAlive()) {
break;
} else {
index++;
}
}
if (index == 500) {
break;
}
}
System.out.println("The result value should be " + 5000000
+ ",actually is"
+ concurrentHashMap.get(HashMapAtomicIntegerTest.KEY));
}
}
class HashMapAtomicCountThread extends Thread {
HashMap<Integer, AtomicInteger> concurrentHashMap = null;
public HashMapAtomicCountThread(
HashMap<Integer, AtomicInteger> concurrentHashMap) {
this.concurrentHashMap = concurrentHashMap;
}
#Override
public void run() {
for (int i = 0; i < 10000; i++) {
concurrentHashMap.get(HashMapAtomicIntegerTest.KEY)
.getAndIncrement();
}
}
}
Results:
The result value should be 5000000,actually is5000000
Or HashMap and synchronized, but much slower than the former
public class HashMapSynchronizeTest {
public static final int KEY = 10;
public static void main(String[] args) {
HashMap<Integer, Integer> hashMap = new HashMap<Integer, Integer>();
hashMap.put(KEY, 0);
List<HashMapSynchronizeThread> threadList = new ArrayList<HashMapSynchronizeThread>();
for (int i = 0; i < 500; i++) {
HashMapSynchronizeThread testThread = new HashMapSynchronizeThread(
hashMap);
testThread.start();
threadList.add(testThread);
}
int index = 0;
while (true) {
for (int i = index; i < 500; i++) {
HashMapSynchronizeThread testThread = threadList.get(i);
if (testThread.isAlive()) {
break;
} else {
index++;
}
}
if (index == 500) {
break;
}
}
System.out.println("The result value should be " + 5000000
+ ",actually is" + hashMap.get(KEY));
}
}
class HashMapSynchronizeThread extends Thread {
HashMap<Integer, Integer> hashMap = null;
public HashMapSynchronizeThread(
HashMap<Integer, Integer> hashMap) {
this.hashMap = hashMap;
}
#Override
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (hashMap) {
hashMap.put(HashMapSynchronizeTest.KEY,
hashMap
.get(HashMapSynchronizeTest.KEY) + 1);
}
}
}
}
Results:
The result value should be 5000000,actually is5000000
Use ConcurrentHashMap will get the wrong results.
public class ConcurrentHashMapTest {
public static final int KEY = 10;
public static void main(String[] args) {
ConcurrentHashMap<Integer, Integer> concurrentHashMap = new ConcurrentHashMap<Integer, Integer>();
concurrentHashMap.put(KEY, 0);
List<CountThread> threadList = new ArrayList<CountThread>();
for (int i = 0; i < 500; i++) {
CountThread testThread = new CountThread(concurrentHashMap);
testThread.start();
threadList.add(testThread);
}
int index = 0;
while (true) {
for (int i = index; i < 500; i++) {
CountThread testThread = threadList.get(i);
if (testThread.isAlive()) {
break;
} else {
index++;
}
}
if (index == 500) {
break;
}
}
System.out.println("The result value should be " + 5000000
+ ",actually is" + concurrentHashMap.get(KEY));
}
}
class CountThread extends Thread {
ConcurrentHashMap<Integer, Integer> concurrentHashMap = null;
public CountThread(ConcurrentHashMap<Integer, Integer> concurrentHashMap) {
this.concurrentHashMap = concurrentHashMap;
}
#Override
public void run() {
for (int i = 0; i < 10000; i++) {
concurrentHashMap.put(ConcurrentHashMapTest.KEY,
concurrentHashMap.get(ConcurrentHashMapTest.KEY) + 1);
}
}
}
Results:
The result value should be 5000000,actually is11759
You could just put the operation in a synchronized (myMap) {...} block.
Your current code changes the values of your map concurrently so this will not work.
If multiple threads can put values into your map, you have to use a concurrent map like ConcurrentHashMap with non thread safe values like Integer. ConcurrentMap.replace will then do what you want (or use AtomicInteger to ease your code).
If your threads will only change the values (and not add/change the keys) of your map, then you can use a standard map storing thread safe values like AtomicInteger. Then your thread will call:map.get(key).incrementAndGet() for instance.

Categories

Resources