2

I couldn't find a clear answer to this question, so apologies if this is nicely answered elsewhere.

Imagine I have something roughly like:

Class SomeClass
{
  List<String> strings = new LinkedList<>();


  public void updateList(List<String> newStrings) {
    strings = newStrings;
  }

  public List<String> getMatchingStrings(String pattern) {
    List<String> matches = new LinkedList<>();
    for (String s : strings) {
      if (matchesPattern(s, pattern)) { //Assume this just works
        matches.add(s);
      }
    }
    return matches;
  }

}

Assuming another thread calls updateList(...) while some thread is calling getMatchingStrings(), will there be a "problem"?

The intended behavior is that the thread currently executing getMatchingStrings() will run on the old list, and a subsequent call will run on the updated list.

Any help is appreciated!

Andreas
  • 154,647
  • 11
  • 152
  • 247
Mike
  • 43
  • 4

3 Answers3

3

There is 'unsafe publication' in you code (no 'happens-before order' between write list field and read list field). Use blocked 'synchronized' or non-blocked 'volatile' or 'AtomicReference':

// with 'synchronized'
class SomeClass0 {
    List<String> strings = new LinkedList<>();

    public synchronized void updateList(List<String> newStrings) {
        this.strings = newStrings;
    }

    public synchronized List<String> getMatchingStrings(String pattern) {
        List<String> matches = new LinkedList<>();
        for (String s : strings) {
            if (matchesPattern(s, pattern)) { //Assume this just works
                matches.add(s);
            }
        }
        return matches;
    }
}

// with 'volatile'
class SomeClass1 {
    volatile List<String> strings = new LinkedList<>();

    public void updateList(List<String> newStrings) {
        this.strings = newStrings;
    }

    public List<String> getMatchingStrings(String pattern) {
        List<String> localCopy = this.strings;
        List<String> matches = new LinkedList<>();
        for (String s : localCopy) {
            if (matchesPattern(s, pattern)) { //Assume this just works
                matches.add(s);
            }
        }
        return matches;
    }
}

// with 'AtomicReference'
class SomeClass2 {
    AtomicReference<List<String>> strings = 
                        new AtomicReference<>(new LinkedList<>());

    public void updateList(List<String> newStrings) {
        this.strings.set(newStrings);
    }

    public List<String> getMatchingStrings(String pattern) {
        List<String> localCopy = this.strings.get();
        List<String> matches = new LinkedList<>();
        for (String s : localCopy) {
            if (matchesPattern(s, pattern)) { //Assume this just works
                matches.add(s);
            }
        }
        return matches;
    }
}
Ivan Golovach
  • 199
  • 2
  • 5
1

If you mean will there be a runtime exception, the answer is no. The code that iterates over strings will use whatever the reference happens to be when the iterator invokes.

It doesn't matter if the strings reference changes while iterating. Think of it like this,

Iterator<String> i = strings.iterator();
strings = new List<String>();
while (i.hasNext()) {
 ...
 i.next();
}

This code is fine. The object pointed to by strings hasn't changed. We only changed the strings reference to point to somewhere else. The iterator still points to the original object referenced by strings before the assignment.

EDIT: some comments below have said the original example is unsafe publication. I don't believe it is. Unsafe publication is about allowing thread B to access an object under construction in thread A- thread A is unsafely publishing the object to thread B.

However, in the example above, there's no object construction with regard to the fields in question. As far as we know both the lists were fully constructed in a thread-safe manner. There is only reference assignment. Object assignment in Java is atomic From the Java language spec,

"When a thread uses the value of a variable, the value it obtains is in fact a value stored into the variable by that thread or by some other thread. This is true even if the program does not contain code for proper synchronization. For example, if two threads store references to different objects into the same reference value, the variable will subsequently contain a reference to one object or the other, not a reference to some other object or a corrupted reference value. (There is a special exception for long and double values; see §17.4.)

Now, there might be unsafe publication in the OP's code if the object passed to updateList() wasn't constructed safely, but I'm not going to hypothesize what bugs might exist in code I can't see.

Jeffrey Blattman
  • 22,176
  • 9
  • 79
  • 134
  • 2
    *However*, the code might pick up the new reference, but without a memory barrier (happen-before), it may not see the complete new list, since that may still be in CPU cache of another CPU. As such, the list may appear corrupt and cause weird results, including exceptions. – Andreas Jun 28 '16 at 22:11
  • @Andreas My assumption would be the VM protects you from that.. Do you have any references to that in practice in Android / Java? I've never heard of it. – Jeffrey Blattman Jun 29 '16 at 00:49
  • 2
    Andreas is correct. This is called unsafe publication. You cannot update references without using some kind of synchronization primitive. Another thread could see an instance of a List that is not up to date. http://stackoverflow.com/questions/18618522/unsafe-publication-concurrency-java – John H Jun 29 '16 at 01:44
  • "this is not possible (thankfully) with newer versions of Java due to updates to the memory model."- http://stackoverflow.com/questions/1621435/not-thread-safe-object-publishing – Jeffrey Blattman Jul 01 '16 at 23:30
  • Literally the next line retracts that statement. – John H Jul 02 '16 at 18:45
  • @Andreas reference assignment is a thread safe operation. This is reference assignment only. – Jeffrey Blattman Jul 03 '16 at 23:42
  • There's more to thread safety than atomicity. There's also visibility. If one thread calls `updateList()` and then another thread calls `getMatchingStrings(...)`, *which* list is seen by `getMatchingStrings(...)` is undefined. This could be fixed by making the reference `volatile`. – Kevin Krumwiede Jul 03 '16 at 23:50
  • @KevinKrumwiede The OP never said he cares which list is seen. Maybe you should ask him? – Jeffrey Blattman Jul 04 '16 at 00:18
  • 2
    @JeffreyBlattman I know reference assignment is safe. I was talking about the values in the referenced object. If the referenced object itself consists of multiple objects, which a linked list definitely does, the total set of those objects may not appear to your thread at the same time, unless you have a happen-before barrier. This means that following those references may give you inconsistent data, which (as I already said) may appear corrupt and cause weird results, including exceptions. – Andreas Jul 04 '16 at 00:47
  • Andreas is correct. Not only is it unspecified which list is visible, but unless the `List` implementation happens to be thread-safe, its contents may not be visible. – Kevin Krumwiede Jul 04 '16 at 00:49
-1

Jeffrey is correct- there is no unsafe publication here, because at no time is one thread constructing the object while another may read it (the instance is protected by the updateList method call). There is, however, significant likelihood of inconsistent access- e.g. the list you end up iterating may not be the list you expect, even if you call updateList first, because there is no explicit memory barrier. The JVM may do weird things at runtime in this situation.

Assuming that the list that ends up being iterated (again, this may not be the list that you expect) is not null, is not modified while iteration is occurring, is properly implemented, etc, etc, then this "pattern" will not cause exceptions. That said, it shouldn't be used- you need to perform synchronization and (probably) mark the local variable with volatile if this will be used in a multithreaded scenario.

Chris Shain
  • 50,833
  • 6
  • 93
  • 125
  • 2
    In general, even a fully-constructed object cannot be safely published without a memory barrier. You can get away with it if the object itself is thread-safe, but the general contract of `List` is not. See [this Q&A](http://stackoverflow.com/questions/24789287/constructors-and-instruction-reordering). – Kevin Krumwiede Jul 04 '16 at 00:11
  • 1
    Fair enough- but, that isn't a problem with this code per se- it's a general assignment problem with Java variables in general and would be fixed by any of the normal memory barrier solutions that this needs anyway. – Chris Shain Jul 04 '16 at 00:16
  • 1
    If the contract of this code is that `updateList(...)` is to be called in one thread and `getMatchingStrings(...)` in another, then it *is* a problem with this code. – Kevin Krumwiede Jul 04 '16 at 00:51