The Memory Model is difficult to understand, and difficult to apply. It is useful if you need to reason about the correctness of multi-threaded code, but you do not want to have to do this reasoning for every multi-threaded application that you write.
If you adopt the following principals when writing concurrent code in Java, you can largely avoid the need to resort to happens-before reasoning.
Use immutable data structures where possible. A properly implemented immutable class will be thread-safe, and will not introduce thread-safety issues when you use it with other classes.
Understand and avoid "unsafe publication".
Use primitive mutexes or Lock
objects to synchronize access to state in mutable objects that need to be thread-safe1.
Use Executor
/ ExecutorService
or the fork join framework rather than attempting to create manage threads directly.
Use the `java.util.concurrent classes that provide advanced locks, semaphores, latches and barriers, instead of using wait/notify/notifyAll directly.
Use the java.util.concurrent
versions of maps, sets, lists, queues and deques rather than external synchonization of non-concurrent collections.
The general principle is to try to use Java's built-in concurrency libraries rather than "rolling your own" concurrency. You can rely on them working, if you use them properly.
1 - Not all objects need to be thread safe. For example, if an object or objects is thread-confined (i.e. it is only accessible to one thread), then its thread-safety is not relevant.