简体   繁体   中英

Threadsafe way of exposing keySet()

This must be a fairly common occurrence where I have a map and wish to thread-safely expose its key set:

public MyClass {
  Map<String,String> map = // ...
  public final Set<String> keys() {
     // returns key set
  }
}

Now, if my "map" is not thread-safe, this is not safe:

  public final Set<String> keys() {
     return map.keySet();
  }

And neither is:

  public final Set<String> keys() {
     return Collections.unmodifiableSet(map.keySet());
  }

So I need to create a copy, such as:

  public final Set<String> keys() {
     return new HashSet(map.keySet());
  }

However, this doesn't seem safe either because that constructor traverses the elements of the parameter and add()s them. So while this copying is going on, a ConcurrentModificationException can happen.

So then:

  public final Set<String> keys() {
     synchronized(map) {
       return new HashSet(map.keySet());
     }
  }

seems like the solution. Does this look right?

That solution isn't particularly helpful unless you plan to also synchronize on the map everywhere it is used. Synchronizing on it doesn't stop someone else from invoking methods on it at the same time. It only stops them from also being able to synchronize on it.

The best solution really seems to be just use ConcurrentHashMap in the first place if you know you need concurrent puts and removes while someone may be iterating. If the concurrency behavior that class offers isn't what you need, you'll probably just need to use a fully synchronized Map.

Good question. I would use Google Guava library. More specifically com.google.common.collect.ImmutableSet.copyOf(Collection<? extends E>) method. In documentation it has been said that this method is thread safe.

If you are interested on thread-safe iterator with exact snapshot of elements through out the iteration process then go for the below.

public class ThreadSafeIteratorConcurrentMap
{
    private ConcurrentMap<String, String> itrSafeMap = null;

    public ThreadSafeIteratorConcurrentCollection() {
            itrSafeMap = new ConcurrentHashMap<String, String>
    }

    public void synchronized put(psConference conference, String p_key)
    {
         itrSafeMap.putIfAbsent(p_key, conference);
    }

    public psConference getConference(String p_key)
    {
        return (itrSafeMap.get(p_key));
    }

    public void synchronized remove(String p_key)
    {
        itrSafeMap.remove(p_key);
    }

    public boolean containsKey(String p_key)
    {
        return itrSafeMap.containsKey(p_key);
    }

    // Get the size of the itrSafeMap.
    public int size()
    {
        return itrSafeMap.size();
    }

    public Iterator<String> valueIterator()
    {
        return (itrSafeMap.values().iterator());
    }   

    public Iterator<String> keyIterator()
    {
        return (itrSafeMap.keySet().iterator());
    }

}

Then where ever you want thread safe iterator with exact snapshot of elements; then use it in synchronized block like below.

   synchronized(threadSafeIteratorConcurrentMapObject) {

       Iterator<String> keyItr = threadSafeIteratorConcurrentMapObject.keyIterator();
       while(keyItr.hasNext()){
        // Do whatever
       }
   }

If you don't mind modification on the collection while iteration; only concentrating on snapshot of elements at the time of iterator creation; then without synchronization block you can use keyItr. Which is already thread safe; it wont through ConcurrentModificationException.

Another option would be to use ConcurrentHashMap. Its keySet() is thread safe so there might be no need to synchronize or take a copy.

您可以使用Collections.UnmodifiableMap创建临时Map,然后迭代键集。

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM