Publishing Non-Thread Safe Object Fields in a Thre

2020-06-16 03:04发布

问题:

I've got a problem with Java concurrency. Yes, I looked at questions with almost the exact same title, but they all seemed to be asking subtly different things. Yes, I've read Java Concurrency in Practice. Yes, I can see why it's the defacto reference for the topic. Yes, I've read the section specifically on publishing fields in thread-safe classes. Yes, I'm still going to ask a concurrency question on Java regardless of the fact that I know someone will simply point me to that book.

This has me stumped though -- I know that you can easily publish mutable primitive fields in a thread-safe manner by ensuring correct read/write orders with volatility and/or synchronized access, and that the 64-bit primitives needs to have atomic access due to the lack of atomicity in its read/write operations. I know about using locks on chunks of code that need to execute on a specific 'snapshot' of the class's fields. I'm fully aware of the atomic package with goodies like AtomicLong<>, etc.

But I'm still confused with regards to publishing non-thread-safe objects as fields in a thread-safe class.

From what I can see, as soon as you return the reference to it in the getter, you've given unprecedented access to the object's contents to the caller which they can use at any point. Also, if you give a setter, you're allowing them to set the object reference to an object they can potentially control outside the object they're using the setter for.

I can't work out anyway of composing a thread-safe class out of non-thread-safe objects without making them all private/protected and creating thread-safe wrapper methods in the class for all the methods all the non-thread safe objects have that the user of the class may want to use. And this just sounds like a boilerplate nightmare.

I mean, if you return an AtomicReference<> to the object in a getter, they can just use .get() to get non-synchronized access to it again.

Another way I considered was to have all the getters return new copies of the non-thread-safe object based on the old one, meaning that modifications would be irrelevant, with the same applying to setters. But Java has a hopelessly complicated system for cloning objects (shallow-copy vs deep-copy vs specific-copying etc), which kinda puts me off doing that. Also, this is so inefficient that it wouldn't be any faster than using a language that's designed for immutability like Clojure. In fact, it would probably be far slower given that such languages allow multiple pieces of immutable data to share the same data behind the scenes.

So, how do I compose thread safe classes of published non-thread safe objects in a viable manner?

Thanks in advance.

回答1:

If reference to unsafe objects has escaped to surrounding threads - you can not do anything to stop other threads to mutate the state, so you should keep the references safe. Make the data private, throw in methods that encapsulate access and modification and make thread-safe copies (yes, cloning is cumbersome) if you ever need to return complex objects.

Try to look at http://en.wikipedia.org/wiki/Law_of_Demeter design principle. quote: In particular, an object should avoid invoking methods of a member object returned by another method. For many modern object oriented languages that use a dot as field identifier, the law can be stated simply as "use only one dot". That is, the code a.b.Method() breaks the law where a.Method() does not. As a simple example, when one wants to walk a dog, it would be folly to command the dog's legs to walk directly; instead one commands the dog and lets it take care of its own legs.

ps: I'm afraid it is open-ended question.