Is iterating ConcurrentHashMap values thread safe? Is iterating ConcurrentHashMap values thread safe? multithreading multithreading

Is iterating ConcurrentHashMap values thread safe?


What does it mean?

That means that each iterator you obtain from a ConcurrentHashMap is designed to be used by a single thread and should not be passed around. This includes the syntactic sugar that the for-each loop provides.

What happens if I try to iterate the map with two threads at the same time?

It will work as expected if each of the threads uses it's own iterator.

What happens if I put or remove a value from the map while iterating it?

It is guaranteed that things will not break if you do this (that's part of what the "concurrent" in ConcurrentHashMap means). However, there is no guarantee that one thread will see the changes to the map that the other thread performs (without obtaining a new iterator from the map). The iterator is guaranteed to reflect the state of the map at the time of it's creation. Futher changes may be reflected in the iterator, but they do not have to be.

In conclusion, a statement like

for (Object o : someConcurrentHashMap.entrySet()) {    // ...}

will be fine (or at least safe) almost every time you see it.


You may use this class to test two accessing threads and one mutating the shared instance of ConcurrentHashMap:

import java.util.Map;import java.util.Random;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ConcurrentMapIteration{  private final Map<String, String> map = new ConcurrentHashMap<String, String>();  private final static int MAP_SIZE = 100000;  public static void main(String[] args)  {    new ConcurrentMapIteration().run();  }  public ConcurrentMapIteration()  {    for (int i = 0; i < MAP_SIZE; i++)    {      map.put("key" + i, UUID.randomUUID().toString());    }  }  private final ExecutorService executor = Executors.newCachedThreadPool();  private final class Accessor implements Runnable  {    private final Map<String, String> map;    public Accessor(Map<String, String> map)    {      this.map = map;    }    @Override    public void run()    {      for (Map.Entry<String, String> entry : this.map.entrySet())      {        System.out.println(            Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']'        );      }    }  }  private final class Mutator implements Runnable  {    private final Map<String, String> map;    private final Random random = new Random();    public Mutator(Map<String, String> map)    {      this.map = map;    }    @Override    public void run()    {      for (int i = 0; i < 100; i++)      {        this.map.remove("key" + random.nextInt(MAP_SIZE));        this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());        System.out.println(Thread.currentThread().getName() + ": " + i);      }    }  }  private void run()  {    Accessor a1 = new Accessor(this.map);    Accessor a2 = new Accessor(this.map);    Mutator m = new Mutator(this.map);    executor.execute(a1);    executor.execute(m);    executor.execute(a2);  }}

No exception will be thrown.

Sharing the same iterator between accessor threads can lead to deadlock:

import java.util.Iterator;import java.util.Map;import java.util.Random;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ConcurrentMapIteration{  private final Map<String, String> map = new ConcurrentHashMap<String, String>();  private final Iterator<Map.Entry<String, String>> iterator;  private final static int MAP_SIZE = 100000;  public static void main(String[] args)  {    new ConcurrentMapIteration().run();  }  public ConcurrentMapIteration()  {    for (int i = 0; i < MAP_SIZE; i++)    {      map.put("key" + i, UUID.randomUUID().toString());    }    this.iterator = this.map.entrySet().iterator();  }  private final ExecutorService executor = Executors.newCachedThreadPool();  private final class Accessor implements Runnable  {    private final Iterator<Map.Entry<String, String>> iterator;    public Accessor(Iterator<Map.Entry<String, String>> iterator)    {      this.iterator = iterator;    }    @Override    public void run()    {      while(iterator.hasNext()) {        Map.Entry<String, String> entry = iterator.next();        try        {          String st = Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']';        } catch (Exception e)        {          e.printStackTrace();        }      }    }  }  private final class Mutator implements Runnable  {    private final Map<String, String> map;    private final Random random = new Random();    public Mutator(Map<String, String> map)    {      this.map = map;    }    @Override    public void run()    {      for (int i = 0; i < 100; i++)      {        this.map.remove("key" + random.nextInt(MAP_SIZE));        this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());      }    }  }  private void run()  {    Accessor a1 = new Accessor(this.iterator);    Accessor a2 = new Accessor(this.iterator);    Mutator m = new Mutator(this.map);    executor.execute(a1);    executor.execute(m);    executor.execute(a2);  }}

As soon as you start sharing the same Iterator<Map.Entry<String, String>> among accessor and mutator threads java.lang.IllegalStateExceptions will start popping up.

import java.util.Iterator;import java.util.Map;import java.util.Random;import java.util.UUID;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ConcurrentMapIteration{  private final Map<String, String> map = new ConcurrentHashMap<String, String>();  private final Iterator<Map.Entry<String, String>> iterator;  private final static int MAP_SIZE = 100000;  public static void main(String[] args)  {    new ConcurrentMapIteration().run();  }  public ConcurrentMapIteration()  {    for (int i = 0; i < MAP_SIZE; i++)    {      map.put("key" + i, UUID.randomUUID().toString());    }    this.iterator = this.map.entrySet().iterator();  }  private final ExecutorService executor = Executors.newCachedThreadPool();  private final class Accessor implements Runnable  {    private final Iterator<Map.Entry<String, String>> iterator;    public Accessor(Iterator<Map.Entry<String, String>> iterator)    {      this.iterator = iterator;    }    @Override    public void run()    {      while (iterator.hasNext())      {        Map.Entry<String, String> entry = iterator.next();        try        {          String st =              Thread.currentThread().getName() + " - [" + entry.getKey() + ", " + entry.getValue() + ']';        } catch (Exception e)        {          e.printStackTrace();        }      }    }  }  private final class Mutator implements Runnable  {    private final Random random = new Random();    private final Iterator<Map.Entry<String, String>> iterator;    private final Map<String, String> map;    public Mutator(Map<String, String> map, Iterator<Map.Entry<String, String>> iterator)    {      this.map = map;      this.iterator = iterator;    }    @Override    public void run()    {      while (iterator.hasNext())      {        try        {          iterator.remove();          this.map.put("key" + random.nextInt(MAP_SIZE), UUID.randomUUID().toString());        } catch (Exception ex)        {          ex.printStackTrace();        }      }    }  }  private void run()  {    Accessor a1 = new Accessor(this.iterator);    Accessor a2 = new Accessor(this.iterator);    Mutator m = new Mutator(map, this.iterator);    executor.execute(a1);    executor.execute(m);    executor.execute(a2);  }}


It means that you should not share an iterator object among multiple threads. Creating multiple iterators and using them concurrently in separate threads is fine.