16

I am using a WeaekHashMap to implement a Cache. I am wondering if I am iterating over the keys of this map, and at the same time garbage collector is actively removing keys from this map, would I receive a ConcurrentModificationException ? I do not think so, because as far as I understand, concurrentmodificationexception happens because of bugs in the application code where the developer forgot to understand that the same map is shared/used by other threads and in this case, it should not happen. But wondering how would JVM handle this when WeakHashMap is not synchronized ?

Shamik
  • 6,938
  • 11
  • 55
  • 72
  • 2
    Weak references are not suitable for caches anyway - they can be cleared immediately, not waiting for the garbage collector (not that waiting for the garbage collector is a great strategy either). – Tom Hawtin - tackline May 18 '10 at 23:13

4 Answers4

15

As bkail said, when GC "removes" an entry from a WeakHashMap it is not going to cause a concurrent modification. In reality the GC collects the underlying object by there is a hard reference to WeakReference object (that holds the real key) itself. Therefore, the real object (the reference object) that is directly referenced by the map is not collected so the map does not change until one of your threads calls a method in this map. At that time the map checks the reference queue from the GC and finds all keys that have been collected and removes them from the map - so the actual changes to the map structure occurs on one of your threads.

In thinking of this then there may be one case where you may get a concurrent modification in such a map that you would not get in another kind of map - if you put a key that already exists or call a getter method. But really, in a concurrent application you should be locking around these calls anyway so you would have a really concurrent access bug in your program.

That being said in answer to your question, you really should not use WeakHashMap for a cache (even if you were talking about caching keys). In a cache you don't want your values 'magically' disappearing when the values are no longer referenced. Typically you want them to disappear when there is a certain maximum number (something like Apache collections LRUMap) or released on memory demand.

For the later, you can use a map with a SoftReference (Apache collections provides a ReferenceMap that allows you to specify the kind of reference for either the key or value). A soft reference is specified to only be released based on memory pressure - a weak reference on the other hand has to do more with GC recognizing that there are no hard reference remaining to the object and can release it at any time. Of course, how a soft reference really works is also dependent on the JVM implementation.

EDIT: I reread your question and want to address one other point. Because the actual modifications occur to the WeakHashMap internal structures on your own thread, if you only use this map in a single thread you do not need to synchronize any method calls. This behavior is no different than any other Map.

Kevin Brock
  • 8,874
  • 1
  • 33
  • 37
  • Thank you Kevin. Very nice explanation, now it is very clear to me. – Shamik May 19 '10 at 02:03
  • Yes, more complete than my answer. +1. – Brett Kail May 19 '10 at 02:17
  • 1) What happens if an entry disappears before the iterator reaches it? Is it simply not returned by `next()`? 2) If I retrieve an entry from `entrySet()` and the key is GCed immediately after that. What happens when I call `entry.getKey()`? – Monstieur May 30 '14 at 17:47
  • 1
    1-Yes; internally the iterator skips any GC'd elements and then keeps a temporary strong reference of the next key once it has determined that the next entry exists so it cannot be GC'd until you retrieve it or advance to the next element. 2-The `Entry` returned from the entry `Set` is a regular object and holds a strong reference to the key/value so it will not be GC'd. – Kevin Brock Jun 08 '14 at 20:36
6

No, you will not receive ConcurrentModificationException. WeakHashMap uses ReferenceQueue.poll when you call various operations. In other words, each caller is silently responsible for clearing stale entries from the Map. However, that does mean that it is not safe to call methods on a WeakHashMap from multiple threads that would otherwise seem to be "read-only" because any call to get() can destroy the entry linked list that another thread is attempting to iterate.

Brett Kail
  • 33,593
  • 2
  • 85
  • 90
0

The WeakHashMap is weak on the keys not the values so it is not appropriate for a cache of values if you want to release space when the value is not being used. You might want to take a look at MapMaker from the google collections.

Ricardo Marimon
  • 10,339
  • 9
  • 52
  • 59
  • 1
    This is true and good advice but it doesn't answer the question. – luke May 18 '10 at 21:59
  • 1
    @luke I think both kinds of answers are valid. Yes, it'd be good to know what iterating over WeakHashMap will do, but if WeakHashMap is the wrong approach given the other context in the question then it's alo reasonable to point that out as an answer. That said, the word "cache" is not enough context to know whether a WeakHashMap is appropriate or not. WeakHashMap has weak keys. It would be appropriate for caching a computation whose parameter is the key of the map (and where said key uses identity equality) if you want the result to have the same lifetime as the key. – Laurence Gonsalves May 18 '10 at 22:14
-2

The documentation is not exactly clear on this but it does say this:

The behavior of the WeakHashMap class depends in part upon the actions of the garbage collector, so several familiar (though not required) Map invariants do not hold for this class. Because the garbage collector may discard keys at any time, a WeakHashMap may behave as though an unknown thread is silently removing entries. In particular, even if you synchronize on a WeakHashMap instance and invoke none of its mutator methods, it is possible for the size method to return smaller values over time, for the isEmpty method to return false and then true, for the containsKey method to return true and later false for a given key, for the get method to return a value for a given key but later return null, for the put method to return null and the remove method to return false for a key that previously appeared to be in the map, and for successive examinations of the key set, the value set, and the entry set to yield successively smaller numbers of elements. - Java API

I think given that description you should expect to occasionally receive ConcurrentModificationExceptions while iterating the map. I would design your cache so you do as little iterating as possible.

luke
  • 14,518
  • 4
  • 46
  • 57
  • Yes this is what I am afraid of. I am anyway not iterating over it but wanted to add some debugging facility to check whats in the cache, it is during that time I had this doubt. – Shamik May 18 '10 at 22:01
  • if it is just for debugging purposes you could probably just copy the keyset and then examine it. – luke May 18 '10 at 22:08
  • 1
    Luke, the "silent thread" is not a real thread so there are no concurrent modifications just because the GC has marked (yes, not actually removed) entries for removal. I use WeakHashMap (not for a cache) in a concurrent environment and have not had such problems. – Kevin Brock May 19 '10 at 00:37