简体   繁体   中英

pure thread-safe Java class

I am recently reading the book "Java Concurrency in Practice". One example of "safe publish" it gives is to initialize a private int field n during construction, and a later assertion on that field n == "expect value" through a public method could still be failed if it is called from another thread. This makes me feel worried in that, assuming all private fields are initialized only once, do we still have to mark them as volatile or wrap them into ThreadLocal or even use an AtomicReference to get a pure thread safe java class, since these private fields, though not visible outside, definitely could be referenced by the method(s) called by other threads.

EDIT: Just to be clear - the assertion is failed because the calling thread sees a stale value of n, even though it has been set during construction. This is a clearly a memory-visibility issue. Problem is whether synchronizing on n is worthy of the overhead after all, since it is only initialized once, and as a private field, author can make sure it won't be changed again.

This specific case is properly documented in the JSR 133: Java Memory Model and Thread Specification . It even has a dedicated code sample page 14 section 3.5 Final Fields that exactly match your question.

To summarize:

  • A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.
  • There is no guarantee for non final fields

It means that you have to make sure that an happens-before occurs between your object creation in a thread and its usage in another thread. You can use synchronized, volatile or any other mean to enforce an happens-before.

Since you say in another comment that the field is only set during construction, I would make it... final. Also, such shared objects between threads could suggest some design smell; I would review my design to make sure that I am not creating an overly complex, tightly coupled, hard to debug system.

If the fields are never used outside the class, wrapping their usages with synchronized blocks or inside synchronized functions, two threads won't concurrently modify these fields.

volatile keyword is just a part of thread safety. It only makes the value of a field never be cached, always read from memory. Take this example.

private int myPrivateField = 0;

void someFunction() {
    while(myPrivateField ==0) {
    }
}

void otherFunction() {
    myPrivateField = 1;
}

If someFunction() is called from one thread and it's running for a while, when you call otherFunction() the value of myPrivateField will not be "updated" inside someFunction , it was cached to 0 as an otimization.

Making myPrivateField as volatile, the value will always be the one in memory.

For the example, there won't be much difference for the functions be synchronized, but without synchronization, you can read a value in an inconsistent state.

Only final fields are guaranteed to be visible after constructor. Any other field requires some visibility mechanism, such as synchronized or volatile .

This is not as much of a burden as it seems: if the field is not final, then it can be changed by another thread while you're reading it. If teh field can be changed, then the last assigned value must be propagated from the writer thread to the reader thread, whether the writer thread called the constructor or a setter.

If the change in this field is not related to any other field in the class, then the field should be volatile . If the field is related to other fields in the class, then use synchronized or other, more modern locking primitives.

Not answering the entire question, but it should be pointed out that using ThreadLocal is exactly the wrong thing to use if you want to ensure visibility of updated values in all threads. Consider the following code:

class Test {
  private static final ThreadLocal<Integer> value = new ThreadLocal<>();

  public static void main(String[] args) throws InterruptedException {
    System.out.println("From main Thread, value is " + value.get());
    value.set(42);
    System.out.println("Value has been changed");
    Thread t = new Thread() {
      public void run() {
        System.out.println("From other Thread, value is " + value.get());
      }
    };
    t.start();
    t.join();
    System.out.println("From main Thread, value is " + value.get());
  }
}

This will output the following:

From main Thread, value is null
Value has been changed
From other Thread, value is null
From main Thread, value is 42

ie the other thread doesn't see the updated value. This is because changes to the value of a ThreadLocal is, by definition, localized to the thread which changes it.

My personal preference would be to use AtomicReference , since this avoids the risk of forgetting to synchronize externally; it also allows things like atomic compare-and-set, which you don't get with a volatile variable. However, this may not be a requirement for your particular application.

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