RFR: jsr166 jdk integration 2018-05

Stuart Marks stuart.marks at oracle.com
Wed May 16 21:21:46 UTC 2018



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





More information about the core-libs-dev mailing list