On 5/15/18 8:02 PM, Martin Buchholz wrote:

How many times the lock is acquired is an implementation detail, a non-obvious tradeoff even. vector.replaceAll holds the lock throughout, but vector.subList(0, size).replaceAll holds the lock for each element (sigh ... racily (really a bug!)).

In Vector's case it's specified, not an implementation detail. You can't perform certain bulk operations on a Vector without holding the lock externally.

        Again, imagine this use case: there is a periodic background task that
        optimizes all the elements of a Vector
        vector.replaceAll(x -> optimized(x))
        That should not break any iterations in progress.

    I don't think it's possible to do that correctly without holding a lock
    around the entire iteration.

I don't see why.

    If the lock is held, CME can't occur, as a concurrent replaceAll() will
    occur before or after the iteration, never in the middle.

I don't see why - iteration is inherently non-atomic, so replaceAll could be called in the middle.

    If an iteration over a Vector doesn't hold a lock, any read-modify-write
    operations (consider a loop with a ListIterator on which set() is called)
    can be interleaved with bulk operations (like replaceAll) which is clearly
    incorrect. In such cases, CME should be thrown.

Imagine your iterators are all read-only, and don't care whether they see an element or its replacement.

You're imagining an incredibly narrow use case. You're choosing Vector, not ArrayList; the replaceAll() operation must an optimization that doesn't affect the semantics of the object (say, the outcome of any logic); the iteration must be read-only, not read-modify-write; and the logic within the iteration "doesn't care" whether it gets old or new values.

I don't find it compelling that it's possible to imagine such a case. Most code won't conform to it. And in fact it's really hard to tell whether it does.

Consider an example like this:

    for (T t : vectorOfT) {
        print(t);
    }

Suppose that a replaceAll() on another thread occurs, and that this is allowed. Does the application care whether the eventual printout contains partly new values and partly old values? How can you tell? It seems to me that this is more likely a programming error than a valid use case.

    Also, this use case cannot be written today, because CME is thrown.

??  Imagine there's only one writer thread, with some Iterating reader threads. Every midnight, the writer thread optimizes all the elements
   for (int i = 0; i < size; i++) vector.set(i, optimized(vector.get(i))
This code has worked well since the '90s without CME.  One day the maintainer notices shiny new Vector.replaceAll and "fixes" the code.
"""It's perfectly safe""".
The CME is rare and so only caught when the maintainer has gone on to the next job.  The CMEs only happen at midnight!

By "cannot be written today" I mean that the following:

    for (T t: arrayList) {
        if (condition) {
            list.replaceAll();
        }
    }

has ALWAYS thrown CME, since the introduction of replaceAll().

Sure, there are cases such as you describe where CME gets thrown only rarely. That's preferable to getting incorrect results equally rarely. That's the point of fail-fast.

**

(subsequent email)

(We don't seem to be moving towards consensus ...)

Apparently....

At the very least, having replaceAll increment modCount (inconsistently!) is 
surprising to users and is not documented anywhere.

Maybe it should be documented then.

**

Why are you placing so much emphasis on *removing* the CME behavior? It's been this way since Java 8 was delivered. Is this causing a problem? What will be solved by removing it?

s'marks



Reply via email to