Is it safe in Java to read (not modify) objects wh

2019-08-13 04:34发布

问题:

there was already a question whether threads can simultaneously safely read/iterate LinkeList. It seems the answer is yes as far as no-one structurally changes it (add/delete) from the linked list.

Although one answer was warning about "unflushed cache" and advicing to know "java memory model". So I'm asking to elaborate those "evil" caches. I'm a newbie and so far I still naively believe that following code is ok (at least from my tests)

public static class workerThread implements Runnable {
    LinkedList<Integer> ll_only_for_read;
    PrintWriter writer;
    public workerThread(LinkedList<Integer> ll,int id2) throws Exception {
        ll_only_for_read = ll;
        writer = new PrintWriter("file."+id2, "UTF-8");
    }
    @Override
    public void run() {
        for(Integer i : ll_only_for_read) writer.println(" ll:"+i);
        writer.close();
    }
}

public static void main(String args[]) throws Exception{
    LinkedList<Integer> ll = new LinkedList<Integer>();
    for(int i=0;i<1e3;i++) ll.add(i);
    // do I need to call something special here? (in order to say:
    // "hey LinkeList flush all your data from local cache
    // you will be now a good boy and share those data among
    // whole lot of interesting threads. Don't worry though they will only read
    // you, no thread would dare to change you"
    new Thread(new workerThread(ll,1)).start();
    new Thread(new workerThread(ll,2)).start();
}

回答1:

Yes, in your specific example code it's okay, since the act of creating the new thread should define a happens-before relationship between populating the list and reading it from another thread." There are plenty of ways that a seemingly-similar set up could be unsafe, however.

I highly recommend reading "Java Concurrency in Practice" by Brian Goetz et al for more details.



回答2:

Although one answer was warning about "unflushed cache" and advicing to know "java memory model".

I think you are referring to my Answer to this Question: Can Java LinkedList be read in multiple-threads safely?.

So I'm asking to elaborate those "evil" caches.

They are not evil. They are just a fact of life ... and they affect the correctness (thread-safety) reasoning for multi-threaded applications.

The Java Memory Model is Java's answer to this fact of life. The memory model specifies with mathematical precision a bunch of rules that need to be obeyed to ensure that all possible executions of your application are "well-formed". (In simple terms: that your application is thread-safe.)

The Java Memory Model is ... difficult.

Someone recommended "Java Concurrency in Practice" by Brian Goetz et al. I concur. It is the best textbook on the topic of writing "classic" Java multi-threaded applications, and it has a good explanation of the Java Memory Model.

More importantly, Goetz et al gives you a simpler set of rules that are sufficient to give you thread-safety. These rules are still too detailed to condense into StackOverflow answer ... but

  • one of the concepts is "safe publication", and
  • one of the principles is to use / re-use existing concurrency constructs rather than to roll your own concurrency mechanisms based on the Memory Model.

I'm a newbie and so far I still naively believe that following code is ok.

It >>is<< correct. However ...

(at least from my tests)

... testing is NOT a guarantee of anything. The problem with non-thread-safe programs is that the faults are frequently not revealed by testing because they manifest randomly, with low probability, and often differently on different platforms.

You cannot rely on testing to tell you that your code is thread-safe. You need to reason1 about the behaviour ... or follow a set of well-founded rules.


1 - And I mean real, well-founded reasoning ... not seat-of-the-pants intuitive stuff.



回答3:

If your code created and populated the list with a single thread and only in a second moment you create other threads that concurrently access the list there is no problem.

Only when a thread can modify a value while other threads try to read the same value can happens problems.

It can be a problem if you change the object you retrieve (also if you don't change the list itself).



回答4:

The way you're using it is fine, but only by coincidence.

Programs are rarely that trivial:

  • If the List contains references to other (mutable) data, then you'll get race conditions.
  • If someone modifies your 'reader' threads later in the code's lifecycle, then you'll get races.

Immutable data (and data structures) are by definition thread-safe. However, this is a mutable List, even though you're making the agreement with yourself that you won't modify it.

I'd recommend wrapping the List<> instance like this so the code fails immediately if someone tries to use any mutators on the List:

List<Integer> immutableList = Collections.unmodifiableList(ll); //...pass 'immutableList' to threads.

Link to unmodifiableList



回答5:

You need to guarantee happens-before relationship between reads and writes in your LinkedList because they are done in separate threads.

Result of ll.add(i) will be visible for new workerThread because Thread.start forms happens-before relationship. So your example is thread safe. See more about happens-before conditions.

However be aware of more complex situation, when LinkedList is read during iteration in worker threads and at the same time it is modified by the main thread. Like this:

for(int i=0;i<1e3;i++) {
    ll.add(i);
    new Thread(new workerThread(ll,1)).start();
    new Thread(new workerThread(ll,2)).start();
}

This way ConcurrentModificationException is possible.

There are several options:

  1. Clone your LinkedList inside of workerThread and iterate the copy instead.
  2. Use synchronization both for list modification and for list iteration (but it will lead to poor concurrency).
  3. Instead of LinkedList use CopyOnWriteArrayList.


回答6:

Sorry for answering to my question. But I was thinking of your reassuring answers and I found it may not be so safe as it seems. I found and tested case when it is not working - if object would use it's class variable for storing any data (I wouldn't know about) then it would fail (then the only question is if linked list (and other java classes) in some implementation can do it...) See failing example:

public class DummyLinkedList {
    public LinkedList<Integer> ll;
    public DummyLinkedList(){
        ll = new LinkedList<Integer>();
    }
    int lastGetIndex;
    int myDummyGet(int idx){
        lastGetIndex = idx;
        //return ll.get(idx); // thids would work fine as parameter is on the stack so uniq for each call (at least if java supports reentrant functions)
        return ll.get(lastGetIndex); // this would make a problem even for only readin the object - question is how many such issues java.* contains
    }
}


回答7:

It depends on how the object was created and made available to your thread. In general, no, it's not safe, even if the object isn't modified.

Following are some ways to make it safe.

First, create the object and perform any modification that is necessary; you can consider the object to be effectively immutable if no more modifications occur. Then, share the effectively immutable object with other threads by one of the following means:

  • Have other threads read the object from a field that is volatile.
  • Write a reference to the object inside a synchronized block, then have other threads read that reference while synchronized on the same lock.
  • Start the reading threads after the object is initialized, passing the object as a parameter. (This is what you are doing in your example, so you are safe.)
  • Pass the object between threads using a concurrent mechanism like a BlockingQueue implementation, or publish it in a concurrent collection, like a ConcurrentMap implementation.

There might be others. Alternatively, you can make all of the fields of the shared object final (including all the fields of its Object members, and so on). Then it will be safe to share this object by any means across threads. That's one of the under-appreciated virtues of immutable types.



回答8:

If you only access to the list is by 'read' methods (including iterations) then you are fine. Like in your code.