简体   繁体   中英

Can a Java collection be safely used outside of synchronized() after initializing in a synchronized() block in the same function?

Is the following code considered thread-safe, ie: is the write to list guaranteed to happen-before the read to list? I've been trying to understand whether this would be considered safe in the Java memory model, but it's unclear.

Through basic flow analysis, it looks like it is guaranteed that all possible threads will have to pass through the synchronized initializer block before hitting the for loop below, but will iteration over that list be deterministic and thread-safe? I'm not sure that initialization is guaranteed to happen-before the use of list below.

Assume that this is the only method in the class. I know that moving iteration inside the synchronized block would guarantee thread-safety, but I'm more interested in knowing whether this construct is safe.

Also, assume that the list never escapes the class.

The Java Memory Model is explained in the JLS here: http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4

private List<Foo> list;
private final Object monitor = new Object();

public void bar() {
    synchronized (monitor) {
        if (list == null) {
            list = new ArrayList<>();
            list.add(...); // expensive operation
            list.add(...); // expensive operation
            list.add(...); // expensive operation
        }
    }

    for (Foo foo : list) {
        // do something with foo
    }
}

It's thread-safe if and only if this is the only place where you structurally modify the list.

If you modify the list somewhere else (for example using clear() ) somewhere else, even if that other place uses synchronized as well , then the list could easily be modified while you are iterating over it .

If you do not intend to modify the list anywhere else, then using Collections.unmodifiableList() to ensure (and document) this fact is probably a good idea.

The JLS #17.4.5 guarantees that:

An unlock on a monitor happens-before every subsequent lock on that monitor.

It also guarantees that the synchronized block can't be executed concurrently and can't be reordered with the for loop.

So the first thread that arrives and gets the monitor (let's call it T0) will initialise the list. When T0 exits the synchronized block, the intra-thread semantics guarantee that the for loop will execute as expected in T0.

All threads that arrive subsequently will wait for the monitor to be available, acquire it and because of the guarantee above will see the list as it was initialised by T0 (ie not null and populated). And because of intra-thread semantics, the for loop will execute as expected.

Conclusion : your code is safe if your list is not written to at other places and all reads are done after acquiring the monitor.

If you never modify your list again appart from within that synchronized method, the code you exposed is safe. Now if some other method (not bar()) uses that same list, then your code is not safe. Also, you should declare final List list

The synchronized(list) only applies to the block of code contained therein. If another thread modifies the list while you iterate through the list using the for each loop you will have a problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM