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.