On Wed, May 16, 2018 at 2:21 PM, Stuart Marks <stuart.ma...@oracle.com>
wrote:

>
>
> 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.
>

What is specified?  Vector is synchronized, but it's unspecified whether
external synchronization via synchronized (vector) {...} works

Vector.replaceAll simply inherits spec from List.replaceAll - it seems
unspecified whether bulk operations like replaceAll on Vector are atomic.
(In fact they are not atomic on the subList).


>         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.
>

Vector is unloved and there are few good uses for it at all - the List
interface is concurrency-hostile.  BUT one good use case for concurrency
here seems to be when there are no structural modifications, when an index
unambiguously identifies a "place".  Sort of like AtomicReferenceArray.  We
could aim towards making Vector and AtomicReferenceArray and
ConcurrentHashMap more useful and cross-compatible (e.g.
Vector.accumulateAndGet0


>     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().
>

But that's a bug (!) and the variant below has never thrown CME, and I
don't want to compound the existing bug.

    for (T t: arrayList) {
        if (condition) {
            list.subList(0, list.size()).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?
>

I started out "simply" optimizing ArrayList subList replaceAll
Then I noticed the obvious implementation would increment modCount.
Which would be introducing a new bug....

Reply via email to